概念

SpringSecurity提供了声明式的安全访问控制解决方案, 对访问权限进行认证和授权, 基于Spring AOP和Servlet过滤器, 提供了安全性方面的全面解决方案.

核心概念:

  • Principle, 代表用户的对象, 还包括一切可以用于验证的设备
  • Authority, 代表用户的角色
  • Permission, 代表授权
  • 验证(Authentication), 指的是建立使用者信息(Principal)的过程

验证过程如下:
1.用户使用用户名和密码登录
2.过滤器(UsernamePasswordAuthenticationFilter)获取到用户名以及密码, 然后封装成Authentication
3.AuthenticationManager认证token
4.AuthenticationManager认证成功, 返回一个封装了用户权限信息的Authentication对象, 用户的上下文信息
5.Authentication对象赋值给当前的SecutiryContext, 建立这个用户的安全上下文
6.用户进行一些受到访问控制机制保护的操作, 访问控制机制会根据当前安全上下文信息检查这个操作所需的权限

  • 授权(Authorization)

核心类

  • SecurityContext, 包含当前正在访问系统的用户的详细信息
  • SecurityContextHolder, 用来保存SecurityContext
  • ProviderManager, 维护一个认证的列表, 以便处理不同的认证方式的认证
  • DaoAuthenticationProvider, AuthenticationProvider最常用的实现, 用来获取用户提交的用户名和密码, 并进行正确性比对, 如果正确, 则返回一个数据库中的用户信息
  • UserDetails, 用户实体类, 包含用户名, 密码, 权限等信息
  • UserDetailService, 用户相关的信息是通过UserDetailService来加载的
  • GrantedAuthority, 每一个GrantedAuthority代表赋予当前用户的一种权限
  • Filter

配置

1.继承WebSecurityConfigurerAdapter, 开启@EnableWebSecurity注解

  1. /**
  2. * @author gavin
  3. * @date 2020-06-20
  4. */
  5. @Configuration
  6. @EnableWebSecurity // 启用web安全配置
  7. @EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法安全设置
  8. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  9. @Autowired
  10. private CustomUserDetailsService userDetailsService;
  11. /**
  12. * 进行权限控制规则相关配置
  13. */
  14. @Override
  15. protected void configure(HttpSecurity http) throws Exception {
  16. http.authorizeRequests()
  17. .antMatchers("/", "/home").permitAll()
  18. // 定义/admin/下的所有的url, 只有拥有admin的角色才能访问
  19. .antMatchers("/admin/**").hasRole("ADMIN")
  20. // 除上面外所有请求都需要鉴权认证
  21. .anyRequest().authenticated()
  22. .and()
  23. .formLogin()
  24. .loginPage("/login")
  25. .permitAll()
  26. .and()
  27. .logout()
  28. .permitAll()
  29. .and()
  30. // 开启CSRF保护
  31. .csrf();
  32. }
  33. /**
  34. * 认证相关, 用来配置全局的认证相关的信息, 包含AuthenticationProvider和UserDetailService
  35. */
  36. @Override
  37. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  38. super.configure(auth);
  39. // 自定义加密规则
  40. // auth.userDetailsService(userDetailsService).passwordEncoder(new PasswordEncoder() {
  41. // @Override
  42. // public String encode(CharSequence charSequence) {
  43. // return null;
  44. // }
  45. //
  46. // @Override
  47. // public boolean matches(CharSequence charSequence, String s) {
  48. // return false;
  49. // }
  50. // });
  51. }
  52. /**
  53. * 进行全局请求忽略规则配置, HttpFireWall配置, debug配置, 全局SecurityFilterChain配置
  54. */
  55. @Override
  56. public void configure(WebSecurity web) throws Exception {
  57. super.configure(web);
  58. }
  59. @Bean
  60. public PasswordEncoder passwordEncoder() {
  61. return new BCryptPasswordEncoder();
  62. }
  63. }

2.实现UserDetailsService接口, 实现loadUserByUsername方法

  1. /**
  2. * @author gavin
  3. * @date 2020-06-20
  4. */
  5. @Service
  6. public class CustomUserDetailsService implements UserDetailsService {
  7. @Override
  8. public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
  9. User user = new User();
  10. user.setUserName("111");
  11. user.setPassword(new BCryptPasswordEncoder().encode("123"));
  12. List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList("1");
  13. user.setGrantedAuthorities(grantedAuthorities);
  14. user.setEnable(true);
  15. return user;
  16. }
  17. }

3.实现UserDetails接口

  1. @Data
  2. public class User implements UserDetails {
  3. private String userName;
  4. private String password;
  5. private Boolean enable;
  6. private Collection<? extends GrantedAuthority> grantedAuthorities;
  7. @Override
  8. public Collection<? extends GrantedAuthority> getAuthorities() {
  9. return this.grantedAuthorities;
  10. }
  11. @Override
  12. public String getPassword() {
  13. return this.password;
  14. }
  15. @Override
  16. public String getUsername() {
  17. return this.userName;
  18. }
  19. /**
  20. * 账户未过期
  21. */
  22. @Override
  23. public boolean isAccountNonExpired() {
  24. return true;
  25. }
  26. /**
  27. * 账户未被锁
  28. */
  29. @Override
  30. public boolean isAccountNonLocked() {
  31. return true;
  32. }
  33. /**
  34. * 账户密码未过期
  35. */
  36. @Override
  37. public boolean isCredentialsNonExpired() {
  38. return true;
  39. }
  40. /**
  41. * 账户启用
  42. */
  43. @Override
  44. public boolean isEnabled() {
  45. return null != this.enable && this.enable;
  46. }
  47. }

多种方式获取用户信息

  1. @GetMapping("/context")
  2. public SecurityContext getSecurityContext(Principal principal, Authentication authentication, HttpServletRequest request) {
  3. String userName = principal.getName();
  4. String userName2 = authentication.getName();
  5. String userName3 = request.getUserPrincipal().getName();
  6. SecurityContext context = SecurityContextHolder.getContext();
  7. // {"authentication":{"authorities":[{"authority":"user"}],"details":{"remoteAddress":"0:0:0:0:0:0:0:1","sessionId":"B8D706FE1ED79D5923F010166C975C4F"},"authenticated":true,"principal":{"password":"$2a$10$FTCyXR.LdQHphI3tXJZL2uObbVlRuo5CJLQCk0A8UNBuqpC0O1f/2","enable":true,"grantedAuthorities":[{"authority":"user"}],"enabled":true,"username":"111","authorities":[{"authority":"user"}],"accountNonExpired":true,"accountNonLocked":true,"credentialsNonExpired":true},"credentials":null,"name":"111"}}
  8. String userName4 = context.getAuthentication().getName();
  9. return context;
  10. }

权限控制

在方法上实现权限控制, 需要启用 @EnableGlobalMethodSecurity, 还需配置启用的类型:
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@EnableGlobalMethodSecurity(securedEnabled= true)

  • JSR-250

JSR, JavaSpecification Requests, java规范提案. JSR-250是用于提供安全方法设置的, 主要提供了注解@RolesAllowed

  • prePostEnabled

基于表达式的注解, 主要提供注解@PreAuthorize, @PostAuthorize, @PreFilter, @PostFilter

  • securedEnabled

提供注解@Secured

JWT

JWT, JSON WEB Token, 是一个开放的标准, 用于在各方之间以JSON对象安全的传输信息, 这些信息通过数字签名进行验证和授权.

JWT请求流程:

  • 用户使用浏览器发送账号和密码
  • 服务器使用私钥创建一个JWT返回给浏览器
  • 浏览器将JWT串在请求头中向服务器发送请求
  • 服务器验证该JWT
  • 根据授权规则返回资源给浏览器

JWT组成:

  • 头部(header): 通过Base 64编码生成的字符串, header中存放的内容说明编码对象是一个jwt
  • 载荷(payload): 主要包含claim, claim是一些实体的状态和额外的元数据
  • 签名(signature): 使用编码后的header和payload及一个秘钥, 使用header中指定签名算法进行签名.