SpringBoot3安全管理

打印 上一主题 下一主题

主题 939|帖子 939|积分 2817

目录

标签:Security.登录.权限;
一、简介

SpringSecurity组件可以为服务提供安全管理的能力,比如身份验证、授权和针对常见攻击的保护,是保护基于spring应用程序的事实上的标准;
在实际开发中,最常用的是登录验证和权限体系两大功能,在登录时完成身份的验证,加载相关信息和角色权限,在访问其他系统资源时,进行权限的验证,保护系统的安全;
二、工程搭建

1、工程结构


2、依赖管理

在starter-security依赖中,实际上是依赖spring-security组件的6.1.1版本,对于该框架的使用,主要是通过自定义配置类进行控制;
  1. <dependency>
  2.     <groupId>org.springframework.boot</groupId>
  3.     <artifactId>spring-boot-starter-security</artifactId>
  4.     <version>${spring-boot.version}</version>
  5. </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.    
  5.     @Resource
  6.     private UserService userService;
  7.     @Resource
  8.     private BCryptPasswordEncoder passwordEncoder;
  9.     @Override
  10.     protected void additionalAuthenticationChecks(
  11.             UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
  12.             throws AuthenticationException {
  13.         User user = (User) userDetails;
  14.         String loginPassword = authentication.getCredentials().toString();
  15.         log.info("user:{},loginPassword:{}",user.getPassword(),loginPassword);
  16.         if (!passwordEncoder.matches(loginPassword, user.getPassword())) {
  17.             throw new AuthException("账号或密码错误");
  18.         }
  19.         authentication.setDetails(user);
  20.     }
  21.     @Override
  22.     protected UserDetails retrieveUser(
  23.             String username, UsernamePasswordAuthenticationToken authentication)
  24.             throws AuthenticationException {
  25.         log.info("username:{}",username);
  26.         return userService.loadUserByUsername(username);
  27.     }
  28. }
复制代码
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
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

西河刘卡车医

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表