SpringBoot3安全管理

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: SpringBoot3安全管理SpringSecurity组件可以为服务提供安全管理的能力,比如身份验证、授权和针对常见攻击的保护,是保护基于spring应用程序的事实上的标准;

标签:Security.登录.权限;

一、简介

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

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

二、工程搭建

1、工程结构

1.png

2、依赖管理

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

<!-- 安全组件 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>${spring-boot.version}</version>
</dependency>

三、配置管理

1、核心配置类

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

@EnableWebSecurity
@EnableMethodSecurity
@Configuration
public class SecurityConfig {
   
   

    /**
     * 基础配置
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
   
   
        // 配置拦截规则
        httpSecurity.authorizeHttpRequests(authorizeHttpRequests->{
   
   
            authorizeHttpRequests
                    .requestMatchers(WhiteConfig.whiteList()).permitAll()
                    .anyRequest().authenticated();
        });
        // 禁用默认的登录和退出
        httpSecurity.formLogin(AbstractHttpConfigurer::disable);
        httpSecurity.logout(AbstractHttpConfigurer::disable);
        httpSecurity.csrf(AbstractHttpConfigurer::disable);

        // 异常时认证处理流程
        httpSecurity.exceptionHandling(exeConfig -> {
   
   
            exeConfig.authenticationEntryPoint(authenticationEntryPoint());
        });

        // 添加过滤器
        httpSecurity.addFilterAt(authTokenFilter(),CsrfFilter.class);
        return httpSecurity.build() ;
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
   
   
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
   
   
        return new AuthExeHandler();
    }

    @Bean
    public OncePerRequestFilter authTokenFilter () {
   
   
        return new AuthTokenFilter();
    }

    /**
     * 认证管理
     */
    @Bean
    public AuthenticationManager authenticationManager() {
   
   
        return new ProviderManager(authenticationProvider()) ;
    }

    /**
     * 自定义用户认证流
     */
    @Bean
    public AbstractUserDetailsAuthenticationProvider authenticationProvider() {
   
   
        return new AuthProvider() ;
    }
}

2、认证数据源

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

@Service
public class UserService implements UserDetailsService {
   
   

    @Resource
    private UserBaseMapper userBaseMapper;
    @Resource
    private BCryptPasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
   
   
        UserBase queryUser = geyByUserName(userName);
        if (Objects.isNull(queryUser)){
   
   
            throw new AuthException("该用户不存在");
        }
        List<GrantedAuthority> grantedAuthorityList = new ArrayList<>() ;
        grantedAuthorityList.add(new SimpleGrantedAuthority(queryUser.getUserRole())) ;
        return new User(queryUser.getUserName(),queryUser.getPassWord(),grantedAuthorityList);
    }

    public int register (UserBase userBase){
   
   
        if (!Objects.isNull(userBase)){
   
   
            userBase.setPassWord(passwordEncoder.encode(userBase.getPassWord()));
            userBase.setCreateTime(new Date()) ;
            return userBaseMapper.insert(userBase) ;
        }
        return 0 ;
    }

    public UserBase getById (Integer id){
   
   
        return userBaseMapper.selectById(id) ;
    }

    public UserBase geyByUserName (String userName){
   
   
        List<UserBase> userBaseList = new LambdaQueryChainWrapper<>(userBaseMapper)
                .eq(UserBase::getUserName,userName).last("limit 1").list();
        if (userBaseList.size() > 0){
   
   
            return userBaseList.get(0) ;
        }
        return null ;
    }
}

3、认证流程

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

@Component
public class AuthProvider extends AbstractUserDetailsAuthenticationProvider {
   
   
    private static final Logger log = LoggerFactory.getLogger(AuthProvider.class);

    @Resource
    private UserService userService;
    @Resource
    private BCryptPasswordEncoder passwordEncoder;

    @Override
    protected void additionalAuthenticationChecks(
            UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
   
   
        User user = (User) userDetails;
        String loginPassword = authentication.getCredentials().toString();
        log.info("user:{},loginPassword:{}",user.getPassword(),loginPassword);
        if (!passwordEncoder.matches(loginPassword, user.getPassword())) {
   
   
            throw new AuthException("账号或密码错误");
        }
        authentication.setDetails(user);
    }
    @Override
    protected UserDetails retrieveUser(
            String username, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
   
   
        log.info("username:{}",username);
        return userService.loadUserByUsername(username);
    }
}

4、身份过滤器

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

@Component
public class AuthTokenFilter extends OncePerRequestFilter {
   
   
    @Resource
    private AuthTokenService authTokenService ;
    @Resource
    private AuthExeHandler authExeHandler ;

    @Override
    protected void doFilterInternal(@Nonnull HttpServletRequest request,
                                    @Nonnull HttpServletResponse response,
                                    @Nonnull FilterChain filterChain) throws ServletException, IOException {
   
   
        String uri = request.getRequestURI();
        if (Arrays.asList(WhiteConfig.whiteList()).contains(uri)){
   
   
            // 如果是白名单直接放行
            filterChain.doFilter(request,response);
        } else {
   
   
            String token = request.getHeader("Auth-Token");
            if (Objects.isNull(token) || token.isEmpty()){
   
   
                // Token不存在,拦截返回
                authExeHandler.commence(request,response,null);
            } else {
   
   
                Object object = authTokenService.getToken(token);
                if (!Objects.isNull(object) && object instanceof User user){
   
   
                    UsernamePasswordAuthenticationToken authentication =
                            new UsernamePasswordAuthenticationToken(user, null,user.getAuthorities());
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                    filterChain.doFilter(request,response);
                } else {
   
   
                    // Token验证失败,拦截返回
                    authExeHandler.commence(request,response,null);
                }
            }
        }
    }
}

四、核心功能

1、登录退出

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

@Service
public class LoginService {
   
   

    private static final Logger log = LoggerFactory.getLogger(LoginService.class);

    @Resource
    private AuthTokenService authTokenService ;
    @Resource
    private AuthenticationManager authenticationManager;

    public String doLogin (UserBase userBase){
   
   
        AbstractAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                userBase.getUserName().trim(), userBase.getPassWord().trim());
        Authentication authentication = authenticationManager.authenticate(authToken) ;
        User user = (User) authentication.getDetails();
        return authTokenService.createToken(user) ;
    }

    public Boolean doLogout (String authToken){
   
   
        SecurityContextHolder.clearContext();
        return authTokenService.deleteToken(authToken) ;
    }
}

@Service
public class AuthTokenService {
   
   

    private static final Logger log = LoggerFactory.getLogger(AuthTokenService.class);
    @Resource
    private RedisTemplate<String,Object> redisTemplate ;

    public String createToken (User user){
   
   
        String userName = user.getUsername();
        String token = DigestUtils.md5DigestAsHex(userName.getBytes());
        log.info("user-name:{},create-token:{}",userName,token);
        redisTemplate.opsForValue().set(token,user,10, TimeUnit.MINUTES);
        return token ;
    }

    public Object getToken (String token){
   
   
        return redisTemplate.opsForValue().get(token);
    }

    public Boolean deleteToken (String token){
   
   
        return redisTemplate.delete(token);
    }
}

2、权限校验

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

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

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

@RestController
public class UserWeb {
   
   

    @Resource
    private UserService userService ;

    @PostMapping("/register")
    public String register (@RequestBody UserBase userBase){
   
   
        return "register-"+userService.register(userBase) ;
    }

    @GetMapping("/select/{id}")
    public UserBase select (@PathVariable Integer id){
   
   
        return userService.getById(id) ;
    }

    @PreAuthorize("hasRole('User')")
    @GetMapping("/user/{id}")
    public UserBase getUser (@PathVariable Integer id){
   
   
        return userService.getById(id) ;
    }

    @PreAuthorize("hasRole('Admin')")
    @GetMapping("/admin/{id}")
    public UserBase getAdmin (@PathVariable Integer id){
   
   
        return userService.getById(id) ;
    }

    @PreAuthorize("hasAnyRole('User','Admin')")
    @GetMapping("/query/{id}")
    public UserBase query (@PathVariable Integer id){
   
   
        return userService.getById(id) ;
    }
}

五、参考源码

文档仓库:
https://gitee.com/cicadasmile/butte-java-note

源码仓库:
https://gitee.com/cicadasmile/butte-spring-parent
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
Java 关系型数据库 MySQL
基于springboot的疫情防控管理系统。Javaee项目,springboot项目。
基于springboot的疫情防控管理系统。Javaee项目,springboot项目。
基于springboot的新生报到注册管理系统(有报告)。Javaee项目,springboot项目。
基于springboot的新生报到注册管理系统(有报告)。Javaee项目,springboot项目。
|
6月前
|
Prometheus 监控 Cloud Native
SpringBoot(七)之监控
Micrometer 是一个应用指标记录库,Spring Boot Actuator 通过它可以与各种监控系统(如 Prometheus、Graphite、New Relic 等)集成。
62 0
|
Java 关系型数据库 MySQL
基于springboot的个人财务系统。Javaee项目,springboot项目。
基于springboot的个人财务系统。Javaee项目,springboot项目。
基于springboot的个人财务系统。Javaee项目,springboot项目。
|
运维 供应链 安全
HIS云综合信息管理系统源码 Spring,SpringBoot
技术架构:Angular+Nginx+Java+Spring,SpringBoot,SpringMVC,SpringSecurity,MyBatisPlus+MySQL + MyCat 系统自主研发、自主版权,提供全面的技术支持,支持电子病历4级。 支持多种业务流程灵活选择,功能配置或业务流程支持参数配置一键切换。 系统采用标准数据接口进行数据交互,具有良好的可扩展性。比如支持第三方软件接口等。 业务系统稳定可靠,客户端采用浏览器Web模式,全面提升系统使用的便捷性、安全性。
|
监控 安全 Java
|
缓存 安全 Java
springboot简单集成shiro权限管理
springboot简单集成shiro权限管理
169 0
springboot简单集成shiro权限管理
|
监控
springboot09、监控
springboot09、监控
101 0
springboot09、监控
|
druid Java 关系型数据库
|
安全 算法 Java
SpringBoot学习笔记(十四:Spring Security安全管理 )
SpringBoot学习笔记(十四:Spring Security安全管理 )
563 0
SpringBoot学习笔记(十四:Spring Security安全管理 )