如何使用SpringSecurity保护程序安全
作者:_元夕 发布时间:2022-09-08 19:57:50
首先,引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
引入此依赖之后,你的web程序将拥有以下功能:
所有请求路径都需要认证
不需要特定的角色和权限
没有登录页面,使用HTTP基本身份认证
只有一个用户,名称为user
配置SpringSecurity
springsecurity配置项,最好保存在一个单独的配置类中:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
配置用户认证方式
首先,要解决的就是用户注册,保存用户的信息。springsecurity提供四种存储用户的方式:
基于内存(生产肯定不使用)
基于JDBC
基于LDAP
用户自定义(最常用)
使用其中任意一种方式,需要覆盖configure(AuthenticationManagerBuilder auth)方法:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}
}
1.基于内存
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("zhangsan").password("123").authorities("ROLE_USER")
.and()
.withUser("lisi").password("456").authorities("ROLE_USER");
}
2.基于JDBC
@Autowired
DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource);
}
基于JDBC的方式,你必须有一些特定表表,而且字段满足其查询规则:
public static final String DEF_USERS_BY_USERNAME_QUERY =
"select username,password,enabled " +
"from users " +
"where username = ?";
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =
"select username,authority " +
"from authorities " +
"where username = ?";
public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY =
"select g.id, g.group_name, ga.authority " +
"from groups g, group_members gm, group_authorities ga " +
"where gm.username = ? " +
"and g.id = ga.group_id " +
"and g.id = gm.group_id";
当然,你可以对这些语句进行一下修改:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username, password, enabled from Users " +
"where username=?")
.authoritiesByUsernameQuery("select username, authority from UserAuthorities " +
"where username=?");
这有一个问题,你数据库中的密码可能是一种加密方式加密过的,而用户传递的是明文,比较的时候需要进行加密处理,springsecurity也提供了相应的功能:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username, password, enabled from Users " +
"where username=?")
.authoritiesByUsernameQuery("select username, authority from UserAuthorities " +
"where username=?")
.passwordEncoder(new StandardPasswordEncoder("53cr3t");
passwordEncoder方法传递的是PasswordEncoder接口的实现,其默认提供了一些实现,如果都不满足,你可以实现这个接口:
BCryptPasswordEncoder
NoOpPasswordEncoder
Pbkdf2PasswordEncoder
SCryptPasswordEncoder
StandardPasswordEncoder(SHA-256)
3.基于LDAP
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userSearchBase("ou=people")
.userSearchFilter("(uid={0})")
.groupSearchBase("ou=groups")
.groupSearchFilter("member={0}")
.passwordCompare()
.passwordEncoder(new BCryptPasswordEncoder())
.passwordAttribute("passcode")
.contextSource()
.root("dc=tacocloud,dc=com")
.ldif("classpath:users.ldif");
4.用户自定义方式(最常用)
首先,你需要一个用户实体类,它实现UserDetails接口,实现这个接口的目的是为框架提供更多的信息,你可以把它看作框架使用的实体类:
@Data
public class User implements UserDetails {
private Long id;
private String username;
private String password;
private String fullname;
private String city;
private String phoneNumber;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return false;
}
}
有了实体类,你还需要Service逻辑层,springsecurity提供了UserDetailsService接口,见名知意,你只要通过loadUserByUsername返回一个UserDetails对象就成,无论是基于文件、基于数据库、还是基于LDAP,剩下的对比判断交个框架完成:
@Service
public class UserService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return null;
}
}
最后,进行应用:
@Autowired
private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder encoder() {
return new StandardPasswordEncoder("53cr3t");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(encoder());
}
配置认证路径
知道了如何认证,但现在有几个问题,比如,用户登录页面就不需要认证,可以用configure(HttpSecurity http)对认证路径进行配置:
@Override
protected void configure(HttpSecurity http) throws Exception {
}
你可以通过这个方法,实现以下功能:
在提供接口服务前,判断请求必须满足某些条件
配置登录页面
允许用户注销登录
跨站点伪造请求防护
1.保护请求
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/design", "/orders").hasRole("ROLE_USER")
.antMatchers(“/”, "/**").permitAll();
}
要注意其顺序,除了hasRole和permitAll还有其它访问认证方法:
方法 | 作用 |
---|---|
access(String) | 如果给定的SpEL表达式的计算结果为true,则允许访问 |
anonymous() | 允许访问匿名用户 |
authenticated() | 允许访问经过身份验证的用户 |
denyAll() | 无条件拒绝访问 |
fullyAuthenticated() | 如果用户完全通过身份验证,则允许访问 |
hasAnyAuthority(String...) | 如果用户具有任何给定权限,则允许访问 |
hasAnyRole(String...) | 如果用户具有任何给定角色,则允许访问 |
hasAuthority(String) | 如果用户具有给定权限,则允许访问 |
hasIpAddress(String) | 如果请求来自给定的IP地址,则允许访问 |
hasRole(String) | 如果用户具有给定角色,则允许访问 |
not() | 否定任何其他访问方法的影响 |
permitAll() | 允许无条件访问 |
rememberMe() | 允许通过remember-me进行身份验证的用户访问 |
大部分方法是为特定方式准备的,但是access(String)可以使用SpEL进一些特殊的设置,但其中很大一部分也和上面的方法相同:
表达式 | 作用 |
---|---|
authentication | 用户的身份验证对象 |
denyAll | 始终评估为false |
hasAnyRole(list of roles) | 如果用户具有任何给定角色,则为true |
hasRole(role) | 如果用户具有给定角色,则为true |
hasIpAddress(IP address) | 如果请求来自给定的IP地址,则为true |
isAnonymous() | 如果用户是匿名用户,则为true |
isAuthenticated() | 如果用户已通过身份验证,则为true |
isFullyAuthenticated() | 如果用户已完全通过身份验证,则为true(未通过remember-me进行身份验证) |
isRememberMe() | 如果用户通过remember-me进行身份验证,则为true |
permitAll | 始终评估为true |
principal | 用户的主要对象 |
示例:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/design", "/orders").access("hasRole('ROLE_USER')")
.antMatchers(“/”, "/**").access("permitAll");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/design", "/orders").access("hasRole('ROLE_USER') && " +
"T(java.util.Calendar).getInstance().get("+"T(java.util.Calendar).DAY_OF_WEEK) == " + "T(java.util.Calendar).TUESDAY")
.antMatchers(“/”, "/**").access("permitAll");
}
2.配置登录页面
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/design", "/orders").access("hasRole('ROLE_USER')")
.antMatchers(“/”, "/**").access("permitAll")
.and()
.formLogin()
.loginPage("/login");
}
// 增加视图处理器
@Overridepublic void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
registry.addViewController("/login");
}
默认情况下,希望传递的是username和password,当然你可以修改:
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/authenticate")
.usernameParameter("user")
.passwordParameter("pwd")
也可修改默认登录成功的页面:
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/design")
3.配置登出
.and()
.logout()
.logoutSuccessUrl("/")
4.csrf攻击
springsecurity默认开启了防止csrf攻击,你只需要在传递的时候加上:
<input type="hidden" name="_csrf" th:value="${_csrf.token}"/>
当然,你也可以关闭,但是不建议这样做:
.and()
.csrf()
.disable()
知道用户是谁
仅仅控制用户登录有时候是不够的,你可能还想在程序的其它地方获取已经登录的用户信息,有几种方式可以做到:
将Principal对象注入控制器方法
将Authentication对象注入控制器方法
使用SecurityContextHolder获取安全上下文
使用@AuthenticationPrincipal注解方法
1.将Principal对象注入控制器方法
@PostMappingpublic String processOrder(@Valid Order order, Errors errors,SessionStatus sessionStatus,Principal principal) {
...
User user = userRepository.findByUsername(principal.getName());
order.setUser(user);
...
}
2.将Authentication对象注入控制器方法
@PostMappingpublic String processOrder(@Valid Order order, Errors errors, SessionStatus sessionStatus, Authentication authentication) {
...
User user = (User) authentication.getPrincipal();
order.setUser(user);
...
}
3.使用SecurityContextHolder获取安全上下文
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();
4.使用@AuthenticationPrincipal注解方法
@PostMappingpublic String processOrder(@Valid Order order, Errors errors,SessionStatus sessionStatus, @AuthenticationPrincipal User user) {
if (errors.hasErrors()) {
return "orderForm";
}
order.setUser(user);
orderRepo.save(order);
sessionStatus.setComplete();
return "redirect:/";
}
来源:https://www.cnblogs.com/NameZZH/p/11457146.html
猜你喜欢
- 一般查询手机归属地内容应该很好用json格式保存,在网上找到了淘宝的归属地API,并下了处理json相关的jar包,做了这个手机归属地查询功
- 1.下载文件,将文件保存到本地。(只试用excel);2.对文件的标题进行检验;3.获取导入的批次(取一个表的一个值,加1);4.循环获取文
- 一、结论先行ArrayList在JDK1.8与JDK1.7底层区别JDK1.7:ArrayList像饿汉式,直接创建一个初始容量为10的数组
- springboot开启事务很简单,只需要一个注解@Transactional 就可以了。因为在springboot中已经默认对jpa、jd
- 本文实例为大家分享了java实现简单斗地主的具体代码,供大家参考,具体内容如下第一种方法 /** * @param args */ /**
- java自定义切面增强写代码时会遇到一些有些重复机械的工作, 这个时候就可以运用切面技术来帮我们节约时间介绍如何使用自定义注解增强方法, 实
- //执行顺序:(优先级从高到低。)静态代码块>mian方法>构造代码块>构造方法。其中静态代码块只执行一次。构造代码块在每
- 本文介绍idea的安装和基本使用首先保证JDK正常安装及配置下载地址:https://www.jetbrains.com/idea/down
- 一、setting.xml文件的位置今天我们来谈谈Maven setting文件配置的禅定之道。不知道大家有没有听说过禅宗?嗯,没错,就是那
- 题目描述Java创建线程的几种方式Java使用Thread类代表线程,所有线程对象都必须是Thread类或者其子类的实例。Java可以用以下
- 使用开源项目JAVAE 进行视频格式转换JAVAE简介:JAVE (Java音频视频编码器)库是ffmpeg项目的Java包装器。开发人员可
- 为什么要重复造轮子你可能会问,Spring已经自带了全局异常拦截,为什么还要重复造轮子呢?这是个好问题,我觉得有以下几个原因装逼Spring
- 昨天有个粉丝加了我,问我如何实现类似shiro的资源权限表达式的访问控制。我以前有一个小框架用的就是shiro,权限控制就用了资源权限表达式
- Arrays.asList()方法的作用是将数组或一些元素转为集合,而你得到的集合并不是我们通常使用的List集合,而是Arrays里面的一
- @ConditionalOnMissingBean,它是修饰bean的一个注解,主要实现的是,当你的bean被注册之后,如果而注册相同类型的
- 利用Java,在控制台操作下,编写的五子棋,作为复习二维数组,面向对象等基础知识。w表示白棋,b表示黑棋import java.util.S
- 一、概述Groovy is a multi-faceted language for the Java platform.Apache Gr
- 前言:什么是JDBCJava 数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户
- 我们在项目中都会遇到项目打包,可以通过assembly对我们的项目进行打包。针对打包构建jar包,本文不再叙述。具体可以参考maven插件a
- 业务场景通常微服务对于用户认证信息解析有两种方案在 gateway 就解析用户的 token 然后路由的时候把 userId 等相关信息添加