经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
SpringBoot3安全管理
来源:cnblogs  作者:知了一笑  时间:2023/8/14 9:17:00  对本文有异议

标签:Security.登录.权限;

一、简介

SpringSecurity组件可以为服务提供安全管理的能力,比如身份验证、授权和针对常见攻击的保护,是保护基于spring应用程序的事实上的标准;

在实际开发中,最常用的是登录验证和权限体系两大功能,在登录时完成身份的验证,加载相关信息和角色权限,在访问其他系统资源时,进行权限的验证,保护系统的安全;

二、工程搭建

1、工程结构

2、依赖管理

starter-security依赖中,实际上是依赖spring-security组件的6.1.1版本,对于该框架的使用,主要是通过自定义配置类进行控制;

  1. <!-- 安全组件 -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-security</artifactId>
  5. <version>${spring-boot.version}</version>
  6. </dependency>

三、配置管理

1、核心配置类

在该类中涉及到的配置非常多,主要是服务的拦截控制,身份认证的处理流程以及过滤器等,很多自定义的处理类通过该配置进行加载;

  1. @EnableWebSecurity
  2. @EnableMethodSecurity
  3. @Configuration
  4. public class SecurityConfig {
  5. /**
  6. * 基础配置
  7. */
  8. @Bean
  9. public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
  10. // 配置拦截规则
  11. httpSecurity.authorizeHttpRequests(authorizeHttpRequests->{
  12. authorizeHttpRequests
  13. .requestMatchers(WhiteConfig.whiteList()).permitAll()
  14. .anyRequest().authenticated();
  15. });
  16. // 禁用默认的登录和退出
  17. httpSecurity.formLogin(AbstractHttpConfigurer::disable);
  18. httpSecurity.logout(AbstractHttpConfigurer::disable);
  19. httpSecurity.csrf(AbstractHttpConfigurer::disable);
  20. // 异常时认证处理流程
  21. httpSecurity.exceptionHandling(exeConfig -> {
  22. exeConfig.authenticationEntryPoint(authenticationEntryPoint());
  23. });
  24. // 添加过滤器
  25. httpSecurity.addFilterAt(authTokenFilter(),CsrfFilter.class);
  26. return httpSecurity.build() ;
  27. }
  28. @Bean
  29. public BCryptPasswordEncoder passwordEncoder(){
  30. return new BCryptPasswordEncoder();
  31. }
  32. @Bean
  33. public AuthenticationEntryPoint authenticationEntryPoint() {
  34. return new AuthExeHandler();
  35. }
  36. @Bean
  37. public OncePerRequestFilter authTokenFilter () {
  38. return new AuthTokenFilter();
  39. }
  40. /**
  41. * 认证管理
  42. */
  43. @Bean
  44. public AuthenticationManager authenticationManager() {
  45. return new ProviderManager(authenticationProvider()) ;
  46. }
  47. /**
  48. * 自定义用户认证流
  49. */
  50. @Bean
  51. public AbstractUserDetailsAuthenticationProvider authenticationProvider() {
  52. return new AuthProvider() ;
  53. }
  54. }

2、认证数据源

UserDetailsService是加载用户特定数据的核心接口,编写用户服务类并实现该接口,提供用户信息和权限体系的数据查询和加载,作为用户身份识别的关键凭据;

  1. @Service
  2. public class UserService implements UserDetailsService {
  3. @Resource
  4. private UserBaseMapper userBaseMapper;
  5. @Resource
  6. private BCryptPasswordEncoder passwordEncoder;
  7. @Override
  8. public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
  9. UserBase queryUser = geyByUserName(userName);
  10. if (Objects.isNull(queryUser)){
  11. throw new AuthException("该用户不存在");
  12. }
  13. List<GrantedAuthority> grantedAuthorityList = new ArrayList<>() ;
  14. grantedAuthorityList.add(new SimpleGrantedAuthority(queryUser.getUserRole())) ;
  15. return new User(queryUser.getUserName(),queryUser.getPassWord(),grantedAuthorityList);
  16. }
  17. public int register (UserBase userBase){
  18. if (!Objects.isNull(userBase)){
  19. userBase.setPassWord(passwordEncoder.encode(userBase.getPassWord()));
  20. userBase.setCreateTime(new Date()) ;
  21. return userBaseMapper.insert(userBase) ;
  22. }
  23. return 0 ;
  24. }
  25. public UserBase getById (Integer id){
  26. return userBaseMapper.selectById(id) ;
  27. }
  28. public UserBase geyByUserName (String userName){
  29. List<UserBase> userBaseList = new LambdaQueryChainWrapper<>(userBaseMapper)
  30. .eq(UserBase::getUserName,userName).last("limit 1").list();
  31. if (userBaseList.size() > 0){
  32. return userBaseList.get(0) ;
  33. }
  34. return null ;
  35. }
  36. }

3、认证流程

自定义用户名和密码的身份令牌认证逻辑,基于用户名Username从上面的用户服务类中加载数据并校验,在验证成功后将用户的身份令牌返回给调用者;

  1. @Component
  2. public class AuthProvider extends AbstractUserDetailsAuthenticationProvider {
  3. private static final Logger log = LoggerFactory.getLogger(AuthProvider.class);
  4. @Resource
  5. private UserService userService;
  6. @Resource
  7. private BCryptPasswordEncoder passwordEncoder;
  8. @Override
  9. protected void additionalAuthenticationChecks(
  10. UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
  11. throws AuthenticationException {
  12. User user = (User) userDetails;
  13. String loginPassword = authentication.getCredentials().toString();
  14. log.info("user:{},loginPassword:{}",user.getPassword(),loginPassword);
  15. if (!passwordEncoder.matches(loginPassword, user.getPassword())) {
  16. throw new AuthException("账号或密码错误");
  17. }
  18. authentication.setDetails(user);
  19. }
  20. @Override
  21. protected UserDetails retrieveUser(
  22. String username, UsernamePasswordAuthenticationToken authentication)
  23. throws AuthenticationException {
  24. log.info("username:{}",username);
  25. return userService.loadUserByUsername(username);
  26. }
  27. }

4、身份过滤器

通过继承OncePerRequestFilter抽象类,实现用户身份的过滤器,如果不是白名单请求,需要验证令牌是否正确有效,SecurityContextHolder默认状态下使用ThreadLocal存储信息;

  1. @Component
  2. public class AuthTokenFilter extends OncePerRequestFilter {
  3. @Resource
  4. private AuthTokenService authTokenService ;
  5. @Resource
  6. private AuthExeHandler authExeHandler ;
  7. @Override
  8. protected void doFilterInternal(@Nonnull HttpServletRequest request,
  9. @Nonnull HttpServletResponse response,
  10. @Nonnull FilterChain filterChain) throws ServletException, IOException {
  11. String uri = request.getRequestURI();
  12. if (Arrays.asList(WhiteConfig.whiteList()).contains(uri)){
  13. // 如果是白名单直接放行
  14. filterChain.doFilter(request,response);
  15. } else {
  16. String token = request.getHeader("Auth-Token");
  17. if (Objects.isNull(token) || token.isEmpty()){
  18. // Token不存在,拦截返回
  19. authExeHandler.commence(request,response,null);
  20. } else {
  21. Object object = authTokenService.getToken(token);
  22. if (!Objects.isNull(object) && object instanceof User user){
  23. UsernamePasswordAuthenticationToken authentication =
  24. new UsernamePasswordAuthenticationToken(user, null,user.getAuthorities());
  25. SecurityContextHolder.getContext().setAuthentication(authentication);
  26. filterChain.doFilter(request,response);
  27. } else {
  28. // Token验证失败,拦截返回
  29. authExeHandler.commence(request,response,null);
  30. }
  31. }
  32. }
  33. }
  34. }

四、核心功能

1、登录退出

自定义登录退出两个接口,基于用户名和密码执行上述的身份认证流程,如果认证成功则返回用户的身份令牌,在请求「非」白名单接口时需要在请求头中Auth-Token:token携带该令牌,在退出时会清除身份信息;

  1. @Service
  2. public class LoginService {
  3. private static final Logger log = LoggerFactory.getLogger(LoginService.class);
  4. @Resource
  5. private AuthTokenService authTokenService ;
  6. @Resource
  7. private AuthenticationManager authenticationManager;
  8. public String doLogin (UserBase userBase){
  9. AbstractAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
  10. userBase.getUserName().trim(), userBase.getPassWord().trim());
  11. Authentication authentication = authenticationManager.authenticate(authToken) ;
  12. User user = (User) authentication.getDetails();
  13. return authTokenService.createToken(user) ;
  14. }
  15. public Boolean doLogout (String authToken){
  16. SecurityContextHolder.clearContext();
  17. return authTokenService.deleteToken(authToken) ;
  18. }
  19. }
  20. @Service
  21. public class AuthTokenService {
  22. private static final Logger log = LoggerFactory.getLogger(AuthTokenService.class);
  23. @Resource
  24. private RedisTemplate<String,Object> redisTemplate ;
  25. public String createToken (User user){
  26. String userName = user.getUsername();
  27. String token = DigestUtils.md5DigestAsHex(userName.getBytes());
  28. log.info("user-name:{},create-token:{}",userName,token);
  29. redisTemplate.opsForValue().set(token,user,10, TimeUnit.MINUTES);
  30. return token ;
  31. }
  32. public Object getToken (String token){
  33. return redisTemplate.opsForValue().get(token);
  34. }
  35. public Boolean deleteToken (String token){
  36. return redisTemplate.delete(token);
  37. }
  38. }

2、权限校验

UserWeb类中提供用户的注册接口,在用户表中创建两个测试用户:admin对应ROLE_Admin角色,user对应ROLE_User角色,验证如下几个接口的权限控制;

select接口不需要鉴权,拦截器放行即可访问;getUser接口校验ROLE_User角色;getAdmin接口校验ROLE_Admin角色;query接口校验两个角色中的任意一个即可;

两个不同用户登录获取到各自的身份令牌,使用不同的令牌请求接口,在PreAuthorize验证通过后才可以正常访问;

  1. @RestController
  2. public class UserWeb {
  3. @Resource
  4. private UserService userService ;
  5. @PostMapping("/register")
  6. public String register (@RequestBody UserBase userBase){
  7. return "register-"+userService.register(userBase) ;
  8. }
  9. @GetMapping("/select/{id}")
  10. public UserBase select (@PathVariable Integer id){
  11. return userService.getById(id) ;
  12. }
  13. @PreAuthorize("hasRole('User')")
  14. @GetMapping("/user/{id}")
  15. public UserBase getUser (@PathVariable Integer id){
  16. return userService.getById(id) ;
  17. }
  18. @PreAuthorize("hasRole('Admin')")
  19. @GetMapping("/admin/{id}")
  20. public UserBase getAdmin (@PathVariable Integer id){
  21. return userService.getById(id) ;
  22. }
  23. @PreAuthorize("hasAnyRole('User','Admin')")
  24. @GetMapping("/query/{id}")
  25. public UserBase query (@PathVariable Integer id){
  26. return userService.getById(id) ;
  27. }
  28. }

五、参考源码

  1. 文档仓库:
  2. https://gitee.com/cicadasmile/butte-java-note
  3. 源码仓库:
  4. https://gitee.com/cicadasmile/butte-spring-parent

原文链接:https://www.cnblogs.com/cicada-smile/p/17627749.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号