写点什么

SpringBoot3 安全管理

作者:知了一笑
  • 2023-08-14
    浙江
  • 本文字数:5195 字

    阅读完需:约 17 分钟

SpringBoot3安全管理

标签:Security.登录.权限;

一、简介

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


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

二、工程搭建

1、工程结构

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


@Servicepublic 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从上面的用户服务类中加载数据并校验,在验证成功后将用户的身份令牌返回给调用者;


@Componentpublic 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存储信息;


@Componentpublic 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携带该令牌,在退出时会清除身份信息;


@Servicepublic 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) ; }}
@Servicepublic 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验证通过后才可以正常访问;


@RestControllerpublic 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
复制代码


发布于: 刚刚阅读数: 4
用户头像

知了一笑

关注

公众号:知了一笑 2020-04-08 加入

源码仓库:https://gitee.com/cicadasmile

评论

发布
暂无评论
SpringBoot3安全管理_Java_知了一笑_InfoQ写作社区