目录
springsecurity之Web权限方案-用户认证
用户认证即设置登录用户名和密码
第一种方式:通过配置文件application.properties
spring.security.user.name=user
spring.security.user.password=123456
第二种方式:通过配置类
-
继承
WebSecurityConfigurerAdapter
-
重写
configure(AuthenticationManagerBuilder auth)
方法 -
将加密器注入容器
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 用auth来设置登录的用户名和密码
// inMemoryAuthentication放入内存中
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String password = passwordEncoder.encode("czs");
auth.inMemoryAuthentication().withUser("czs").password(password).roles("admin");
}
/**
* 报错There is no PasswordEncoder mapped for the id "null"
* 因为springsecurity加密的时候如果不传使用那种加密器进行加密,springsecurity不知道使用哪种加密器进行解密
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
第三种方式:自定义实现类(重点)
springsecurity在认证过程中,会先去配置文件和配置类中找用户名和密码的相关信息,如果没有设置就会去UserDetailsServie
接口中找设置用户名和密码的方式
- 创建配置类,设置使用哪个
UserDetailsServie
实现类,并将其注入到spring中 - 编写实现类,返回User对象,User对象有用户名密码和操作权限
第一步操作:创建配置类,设置使用哪个UserDetailsServie
实现类,并将其注入到spring中
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 编写实现类注入
*/
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 设置自定义的userDetailsService实现类及用到的加密器类型
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
/**
* 报错There is no PasswordEncoder mapped for the id "null"
* 因为springsecurity加密的时候如果不传使用那种加密器进行加密,springsecurity不知道使用哪种加密器进行解密
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
第二步操作:编写实现类,返回User对象,User对象有用户名密码和操作权限
AuthorityUtils.commaSeparatedStringToAuthorityList
权限工具类的逗号分割字符串到授权列表
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User("czs", new BCryptPasswordEncoder().encode("123456"), authorities);
}
}
查询数据库实现用户认证
整合Mybatis-Plus实现,核心代码重写loadUserByUsername
即上述的第三种方式:自定义实现类整合了Mybatis-Plus
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
com.edu.security.entity.User user = userService.getOne(new QueryWrapper<com.edu.security.entity.User>().eq("username", s));
if (StringUtils.isEmpty(user)) {
// 数据库中没有用户名 认证失败
// 执行认证失败的方法
throw new UsernameNotFoundException("用户不存在");
}
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User(user.getUsername(), new BCryptPasswordEncoder().encode(user.getPassword()), authorities);
}
}
自定义用户登录页面
在配置类中实现相关配置
重写configure(HttpSecurity http)
方法
@Override
protected void configure(HttpSecurity http) throws Exception {
// 关闭csrf防护
http.csrf().disable();
// 自定义直接编写的登录页面
http.formLogin()
.loginPage("/login.html")// 登录页面设置
.loginProcessingUrl("/user/login") // 登录访问路径
.defaultSuccessUrl("/test/index") // 登录成功之后,跳转路径
.permitAll();
// 定义哪些路径需要认证访问
http.authorizeRequests()
.antMatchers("/", "/test/hello", "/user/login") // 设置哪些路径可以直接访问,不需要认证
.permitAll().anyRequest().authenticated();
}
登录页面提交的用户名密码参数name值必须是username
和password
,原因是UsernamePasswordAuthenticationFilter
这个过滤链只会接受表单提交上来的username
和password
的值
springsecurity之Web权限方案-用户授权
基于权限访问控制
hasAuthority:如果当前的主体具有指定的权限,则返回true否则返回false
hasAnyAuthority:如果当前的主体有任何一个给定的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true
在配置类中设置当前访问地址有哪些权限
@Override
protected void configure(HttpSecurity http) throws Exception {
....
http.authorizeRequests()
.antMatchers("/test/admin")
.hasAuthority("admin") // 当前登录用户,只有具有admin权限才可以访问这个路径
.anyRequest().authenticated();
http.authorizeRequests()
.antMatchers("/test/admin")
.hasAnyAuthority("admin,user") // 当前登录用户,有admin,user其中一个权限即可访问
.anyRequest().authenticated();
}
在UserDetailsService中给返回的User对象设置权限
....
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
return new User(user.getUsername(), new BCryptPasswordEncoder().encode(user.getPassword()), authorities);
有权限就能成功访问,没有权限则返回403
基于角色访问控制
hasRole:如果用户具备给定角色的允许访问,如果当前主体具有指定的角色,则返回true,否则出现403
hasAnyRole:表示用户具备任何一个条件都可以访问
在配置类中设置当前访问地址有哪些角色可以访问
@Override
protected void configure(HttpSecurity http) throws Exception {
// 关闭csrf防护
http.csrf().disable();
...
// 定义哪些路径需要认证访问
http.authorizeRequests()
.antMatchers("/", "/test", "/user/login") // 设置哪些路径可以直接访问,不需要认证
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/test/admin")
.hasRole("admin") // 当前登录用户,只有具有admin角色才可以访问
.anyRequest().authenticated();
// 定义哪些路径需要认证访问
http.authorizeRequests()
.antMatchers("/", "/test", "/user/login") // 设置哪些路径可以直接访问,不需要认证
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/test/admin")
.hasAnyRole("admin,user") // 当前登录用户,有admin,user其中一个角色即可访问
.anyRequest().authenticated();
}
在UserDetailsService中给返回的User对象设置权限
注意,角色权限要加上**ROLE_**前缀
....
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin,user");
return new User(user.getUsername(), new BCryptPasswordEncoder().encode(user.getPassword()), authorities);
自定义403页面
如果不自定义403页面,系统默认
配置类中配置自定义403页面
http.exceptionHandling().accessDeniedPage()
@Override
protected void configure(HttpSecurity http) throws Exception {
....
// 配置自定义403页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
}
使用注解进行认证授权
使用@Secured注解先要开启注解功能
@EnableGlobalMethodSecurity(securedEnabled = true)
使用@Prexxx或者@Postxxx注解先要开启注解功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Secured:判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀"ROLE_"
@GetMapping("test/admin")
@Secured({"ROLE_admin","ROLE_super"})
public R testAdmin() {
return R.ok().data("data", "只有admin用户可以访问");
}
@PreAuthorize:该注解适合进入方法前的权限验证,@PreAuthorize可以将登录用户的roles/permissions参数传到方法中
@GetMapping("test/admin")
@PreAuthorize("hasAnyAuthority('admin')")
public R testAdmin() {
return R.ok().data("data", "只有admin用户可以访问");
}
@PostAuthorize:该注解使用不多,在方法执行后再进行权限验证,适合验证带有返回值的权限
@GetMapping("test/admin")
@PostAuthorize("hasAnyAuthority('admin')")
public R testAdmin() {
return R.ok().data("data", "只有admin用户可以访问");
}
@PostFilter:权限验证之后,对数据进行过滤,留下用户名是admin1的数据
表达式中的filterObject引用的是返回值LIst中的某一个元素
@GetMapping("test/admin")
@PostAuthorize("hasRole('ROLE_admin')")
@PostFilter("filterObject.username=='admin1'")
public R testAdmin() {
ArrayList<Object> list = new ArrayList<>();
list.add(new UserInfo(11,"admin1","1111"));
list.add(new UserInfo(22,"admin2","2222"));
return R.ok().data("data", list);
}
@PreFilter:进入控制器之前对数据进行过滤
@GetMapping("test/admin")
@PreAuthorize("hasRole('ROLE_admin')")
@PreFilter("filterObject.username=='admin1'")
public R testAdmin() {
ArrayList<Object> list = new ArrayList<>();
list.add(new UserInfo(11,"admin1","1111"));
list.add(new UserInfo(22,"admin2","2222"));
return R.ok().data("data", list);
}
用户注销
在配置类中添加退出映射地址
@Override
protected void configure(HttpSecurity http) throws Exception {
....
// logoutUrl退出登录请求路径 logoutSuccessUrl退出登录成功跳转的路径
http.logout().logoutUrl("/logout"). logoutSuccessUrl("/logout/index").permitAll();
}
登录成功之后,访问/logout
退出登录成功后自动跳转到logout/index
,再次去访问需要权限的请求需要重新登录才能访问
自动登录
实现原理
第一次访问的时候,用户登录认证请求UsernamePasswordAuthenticationFilter
过滤器,认证成功后调用successfulAuthentication
方法由RememberMeService
做相应的处理将会向浏览器和数据库分别发送一份用户的token信息。再次访问的时候,请求RememberMeAuthenticationFilter
过滤器获取cookie信息,调用check方法
拿着cookie信息到数据库进行比对,如果查询到对应的信息,则认证成功进行登录
功能实现
创建数据表
JdbcTokenRepositoryImpl
中的CREATE_TABLE_SQL脚本
create table persistent_logins (
username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null)
配置类中注入数据源,配置操作数据库对象
/**
* 注入数据源
*/
@Autowired
private DataSource dataSource;
/**
* 配置对象
*
* 注入JdbcTokenRepositoryImpl上层接口更好 new的是具体实现对象
* 将数据源注入进JdbcTokenRepositoryImpl
*/
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
// 自动生成数据表,如果手动创建就不用开启
// jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
配置类中配置自动登录
configure(HttpSecurity http)
@Override
protected void configure(HttpSecurity http) throws Exception {
....
// 记住我相关设置 设置token数据库对象 设置cookie的有效时长 设置查询数据库的userDetailsService
http.rememberMe()
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60)
.userDetailsService(userDetailsService);
}
登录页面添加remember-me复选框
此处:name属性值必须为remember-me,不能改为其他值,spring security指定的
cookie和数据库已经存入加密信息
测试,登录成功之后,关闭浏览器,重新访问需要权限的请求,请求成功
CSRF功能
简介
跨站请求伪造(英语:Cross-site request forgery),也被称为one-click、attack或者session riding,通常缩写为CSRF或者XSRF,是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS利用的是用户对指定网站的信任,CSRF利用的是网站对用户网页浏览器的信任。
跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
从 Spring Security 4.0开始,默认情况下会启用CSRF保护,以防止CSRF攻击应用程序,Spring Security CSRF 会针对 PATCH,POST,PUT和DELETE方法进行防护。
使用方法
引入依赖 对thymeleaf添加springsecurity标签支持
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
在登录页面添加一个隐藏域
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
底层原理
生成csrfToken保存到HttpSession或者Cookie中CsrfFilter
过滤器