SpringBoot中SpringSecurity安全框架的基本配置与使用

Spring Security是一个基于Spring框架的安全性解决方案,提供了全面的安全功能和集成能力,用于保护Java应用程序的身份验证、授权和其他安全需求。

Spring Security的主要功能包括:

  1. 身份验证(Authentication):Spring Security提供了多种身份验证机制。
  2. 授权(Authorization):Spring Security支持基于角色和权限的授权机制,可以精确控制用户对系统资源的访问权限。它提供了注解和标签,使开发人员可以在代码中灵活定义和配置授权规则。
  3. 认证流程的安全控制:Spring Security提供了很多机制来确保认证流程的安全性。
  4. Web安全:Spring Security可以通过过滤器链的方式保护Web应用程序的安全性。
  5. 方法级安全:Spring Security允许在方法级别上进行安全控制,通过注解或XML配置来限制对特定方法的访问权限。
  6. 安全事件和审计:Spring Security提供了安全事件机制,可以记录和处理安全相关的事件,例如登录成功、权限拒绝等。它还支持审计功能,可用于记录和追踪用户的操作行为。

Spring Security是一个功能强大、灵活可扩展的安全框架,可以帮助开发人员在Java应用程序中实现全面的身份验证和授权功能,提高应用程序的安全性和可信度。

在SpringBoot中使用首先需要导入依赖

<!--        SpringSecurity-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

然后配置SecurityConfig和Jwt工具类

在SecurityConfig中可以对访问权限进行设置,将登录以及注册接口设置为开放(不然系统就无法访问),也可以将不需要进行权限认证的接口也再此设置为开发;同时也可以通过注解的方式对需要进行权限认证的接口进行设置。

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

    @Autowired
    private JwtAuthenticationTokenFilter filter;

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Resource
    private AccessDeniedHandler accessDeniedHandler;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()

                // 对于登录接口 允许匿名访问 未登录状态也可以访问
                .antMatchers("/login/login").anonymous()
                .antMatchers("/login/register").anonymous()
                .antMatchers("/login/sendCode").anonymous()
                .antMatchers("/pay/notify").anonymous()
                // 需要用户带有管理员权限
//                .antMatchers("/find").hasRole("管理员")
//                // 需要用户具备这个接口的权限
//                .antMatchers("/find").hasAuthority("menu:user")

                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
        //添加过滤器
        http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);

        //配置异常处理器
        http.exceptionHandling()
                //配置认证失败处理器
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler);

        //允许跨域
        http.cors();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}
public class JwtUtil {

    //有效期为
    public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000  一个小时
    //设置秘钥明文
    public static final String JWT_KEY = "sangeng";

    public static String getUUID(){
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        return token;
    }

    /**
     * 生成jtw
     * @param subject token中要存放的数据(json格式)
     * @return
     */
    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
        return builder.compact();
    }

    /**
     * 生成jtw
     * @param subject token中要存放的数据(json格式)
     * @param ttlMillis token超时时间
     * @return
     */
    public static String createJWT(String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
        return builder.compact();
    }

    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if(ttlMillis==null){
            ttlMillis=JwtUtil.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid)              //唯一的ID
                .setSubject(subject)   // 主题  可以是JSON数据
                .setIssuer("sg")     // 签发者
                .setIssuedAt(now)      // 签发时间
                .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
                .setExpiration(expDate);
    }

    /**
     * 创建token
     * @param id
     * @param subject
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id, String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
        return builder.compact();
    }

    public static void main(String[] args) throws Exception {
        String jwt = createJWT("1234");
        Claims claims = parseJWT(jwt);
        String subject = claims.getSubject();

        System.out.println(subject);
    }

    /**
     * 生成加密后的秘钥 secretKey
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

    /**
     * 解析
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }


}

定义用户类,实现UserDetails接口,对用户权限进行封装。

@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {

    private User user;

    private List<String> permissions;

    public LoginUser(User user, List<String> permissions) {
        this.user = user;
        this.permissions = permissions;
    }


    //返回权限信息

    @JSONField(serialize = false)  //不需要存到redis中,进行序列化忽略
    private List<SimpleGrantedAuthority> authorities;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        if (authorities != null){
            return authorities;
        }
       authorities = permissions.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());

        return authorities;
    }

    @Override
    public String getPassword() {
        return user.getUserPwd();
    }

    @Override
    public String getUsername() {
        return user.getUserName();
    }

    //判断是否没过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

实现UserDetailsService接口,重写loadUserByUsername方法查询用户权限信息

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

    //查询用户信息
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(User::getUserName, username);
    User user = userMapper.selectOne(wrapper);
    //如果没有查询到用户就抛出异常
    if (Objects.isNull(user)){
        throw new RuntimeException("用户名或密码错误");
    }
    List<String> list = menuMapper.selectPermsByUserId(user.getId());

    return new LoginUser(user,list);
}

配置过滤器,

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private RedisCache redisCache;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("token");

        if (!StringUtils.hasText(token)) {
            //token为空,放行
            filterChain.doFilter(request, response);
            return;
        }
        //解析token
        String userId;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userId = claims.getSubject();
        } catch (Exception e) {
            throw new RuntimeException("token非法");
        }
        //从redis中获取用户信息
        String redisKey = "login:" + userId;

        LoginUser loginUser = redisCache.getCacheObject(redisKey);
        if (Objects.isNull(loginUser)){
            throw new RuntimeException("用户未登录");
        }
        //存入securityContextHolder
        // 获取权限信息封装到Authentication中
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());

        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        //放行
        filterChain.doFilter(request, response);
    }
}

配置认证失败和权限不足的返回类,这两个方法,在SecurityConfig中进行添加。当对于清空出现时,调用方法,返回给前端状态码,以及自定义信息。

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        Result result = new Result(HttpStatus.UNAUTHORIZED.value(), "认证失败");
        String json = JSON.toJSONString(result);
        //处理异常
        WebUtils.renderString(response, json);

    }
}
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        Result result = new Result(HttpStatus.FORBIDDEN.value(), "权限不足");
        String json = JSON.toJSONString(result);
        WebUtils.renderString(response, json);
    }
}

配置完成后,在前端页面访问后端接口,过滤器会进行拦截,判断前端请求中请求头是否携带token,携带并成功验证用户后,会将用户的权限信息封装到Authentication中,后续判断该用户是否拥有访问该接口的权限。

例如设置下面接口的访问中需要用户拥有getUserAddress权限。

@RequestMapping("/getUserAddress")
@PreAuthorize("hasAuthority('getUserAddress')")
public Result getUserAddress(Long userId) {

    return addressService.getUserAddress(userId);
}

用户、角色、权限表信息如下
在这里插入图片描述
SpringBoot中SpringSecurity安全框架的基本配置与使用_第1张图片
SpringBoot中SpringSecurity安全框架的基本配置与使用_第2张图片
SpringBoot中SpringSecurity安全框架的基本配置与使用_第3张图片

SpringBoot中SpringSecurity安全框架的基本配置与使用_第4张图片

可以看到用户1不存在访问getUserAddress的权限。

当使用用户1登录后,在前端对该接口进行访问时,后端会返回权限不足的状态码以及相应的信息信息。该接口的方法也不会执行。
SpringBoot中SpringSecurity安全框架的基本配置与使用_第5张图片

你可能感兴趣的:(Security,spring,boot,后端)