9、SpringSecurity+JWT+RSA分布式版实战

Spring Security+JWT+RSA分布式版实战

注意下面所有代码类中代码,导包缺失序列化工具类、用户缓存工具类、JWT、RSA、统一异常、统一响应格式等工具类,请到博主其他文章中获取。

1、导包

    implementation "org.springframework.boot:spring-boot-starter-security:2.6.5"
    implementation "io.jsonwebtoken:jjwt-api:0.10.7"
    implementation "io.jsonwebtoken:jjwt-impl:0.10.7"
    implementation "io.jsonwebtoken:jjwt-jackson:0.10.7"
    implementation "joda-time:joda-time:2.10.1"

注意:博主使用的不是Maven,是Gradle。

2、配置类

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

/**
 * @author :
 * @date :Created in 14:38 2022/9/7
 * @description :
 * @version: 1.0
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final RsaKeyProperties prop;
    private final SecurityUserDetailsService securityUserDetailsService;
    private final BCryptPasswordEncoder passwordEncoder;
    private final LogoutSuccessHandler logoutSuccessHandler;
    private final VerifyAuthenticationEntryPoint verifyAuthenticationEntryPoint;
    private final IUserCacheService iUserCacheService;
    private final LoginUserAccessDeniedHandler accessDeniedHandler;
    private final LoginFailureHandler loginFailureHandler;

    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers(permitUrls);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        JwtLoginFilter jwtLoginFilter = new JwtLoginFilter(super.authenticationManager());
        jwtLoginFilter.setAuthenticationFailureHandler(loginFailureHandler);
        http.cors().and().csrf().disable();
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and().addFilter(jwtLoginFilter)
                .addFilter(new JwtVerifyFilter(super.authenticationManager(), prop, iUserCacheService, verifyAuthenticationEntryPoint))
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().exceptionHandling()
                .authenticationEntryPoint(verifyAuthenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler)
                .and().formLogin().permitAll()
                .and().logout().permitAll()
                .logoutSuccessHandler(logoutSuccessHandler)
        ;

    }

    /**
     * 认证授权控制器 (管理者 提供者 认证者 授权者)
     * @param auth
     * @return void
     * @Author 
     * @Date Created in 10:31 2022/9/6
     * @Description
     **/
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(securityUserDetailsService).passwordEncoder(passwordEncoder);
    }

    /**
     * 放过Url
     * @Author 
     * @Date Created in 11:34 2022/9/11
     * @Description
     * @param
     * @return
     **/
    String[] permitUrls = new String[]{
            "/" + VERSION_NO + "/test/**"
    };
}

3、UserDetailsService和SecurityUser

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

/**
 *
 * @Author 
 * @Date Created in 16:57 2022/9/31
 * @Description
 * @param
 * @return
 **/
@Component
@Slf4j
@RequiredArgsConstructor
public class SecurityUserDetailsService implements UserDetailsService {

   private final IUserService userService;
   private final IUserCacheService iUserCacheService;
   private final RsaKeyProperties prop;

   @Override
   public UserDetails loadUserByUsername(String username) {
      User user = userService.findByUsername(username);
      checkUserInfo(user);
      return new SecurityUser(user, getJti(user));
   }

   private void checkUserInfo(User user) {
      if(ObjectUtils.isEmpty(user)){
         log.info("登录失败,查询不到用户信息");
         throw new UsernameNotFoundException("账户不存在");
      }
      if(ObjectUtils.isEmpty(user.getRoles())){
         log.info("登录失败,未绑定角色,不允许登录!");
         throw new BadCredentialsException("用户未绑定角色,不允许登录,请联系管理员!");
      }
   }

   private String getJti(User user) {
      JwtClaim jwtClaim = JwtClaim.toJwtClaim(user);
      String jwt = JwtUtils.generateTokenExpireInMinutes(jwtClaim, prop.getPrivateKey(), 24 * 60 * 7);
      String jti = JwtUtils.getInfoFromToken(jwt, prop.getPublicKey(), JwtClaim.class).getId();
      iUserCacheService.cacheJwtAndUser(jti, jwt, user);
      return jti;
   }
}



/**
 * @author :
 * @date :Created in 9:46 2022/6/29
 * @description :
 * @version: 1.0
 */
public class SecurityUser implements UserDetails, Serializable {

    private User user;

    private String jti;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authList = new ArrayList<>();
        for(Role role:this.user.getRoles()){
            //1.1角色关键词
            authList.add(new SimpleGrantedAuthority(role.getKeyword()));
            for (Permission permission:role.getPermissions()){
                //1.2权限关键词
                authList.add(new SimpleGrantedAuthority(permission.getKeyword()));
            }
        }
        return authList;
    }

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

    @Override
    public String getUsername() {
        return ObjectUtils.isEmpty(this.user) ? "" :this.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 UserStatusEnum.EFFECTIVE.getCode().equals(this.user.getState());
    }

    public SecurityUser(User user, String jti) {
        this.user = user;
        this.jti = jti;
    }

    public SecurityUser() {
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public String getJti() {
        return jti;
    }

    public void setJti(String jti) {
        this.jti = jti;
    }
}

4、登录处理

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

/**
 * @author :
 * @date :Created in 16:25 2022/9/11
 * @description :
 * @version: 1.0
 */
@Slf4j
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    public JwtLoginFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }


    @SneakyThrows
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
        log.info("[login]接收到请求");
        User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
        log.info("[login]username:{}", user.getUsername());
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
        return authenticationManager.authenticate(authRequest);
    }

    @Override
    public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException {
        log.info("[login]认证成功");
        response.addHeader(HttpHeaders.AUTHORIZATION, JwtUtils.BEARER_PREFIX + ((SecurityUser)authResult.getPrincipal()).getJti());
        response.setContentType(APPLICATION_JSON_VALUE);
        response.setStatus(HttpServletResponse.SC_OK);
        log.info("[login]返回token");
        ServletUtils.render(request, response, Result.success(null));
    }

}

5、校验处理

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 检验(认证授权)
 * @Author 
 * @Date Created in 15:33 2022/9/29
 * @Description
 * @param
 * @return
 **/
public class JwtVerifyFilter extends BasicAuthenticationFilter {

    private RsaKeyProperties prop;
    private IUserCacheService iUserCacheService;
    private VerifyAuthenticationEntryPoint verifyAuthenticationEntryPoint;

    public JwtVerifyFilter(AuthenticationManager authenticationManager, RsaKeyProperties prop, IUserCacheService iUserCacheService, VerifyAuthenticationEntryPoint verifyAuthenticationEntryPoint) {
        super(authenticationManager);
        this.prop = prop;
        this.iUserCacheService = iUserCacheService;
        this.verifyAuthenticationEntryPoint = verifyAuthenticationEntryPoint;
    }

    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            String jtiInfo = request.getHeader(HttpHeaders.AUTHORIZATION);
            checkJti(jtiInfo);
            String jti = jtiInfo.replace(BEARER_PREFIX, "");
            String jwt = iUserCacheService.getJwtByJti(jti);
            checkJwt(jwt);
            Payload<JwtClaim> payload = JwtUtils.getInfoFromToken(jwt, prop.getPublicKey(), JwtClaim.class);
            UsernamePasswordAuthenticationToken authResult = new UsernamePasswordAuthenticationToken("", null, null);;
            if (!payload.getUserInfo().getClientType().equals(ClientTypeEnum.TO_C.getId())) {
                SecurityUser securityUser = new SecurityUser(iUserCacheService.getUserAndRenewalByUserNameAndJti(payload.getUserInfo().getUsername(), jti), jti);
                authResult = new UsernamePasswordAuthenticationToken(securityUser, securityUser.getPassword(), securityUser.getAuthorities());
            }
            SecurityContextHolder.getContext().setAuthentication(authResult);
            chain.doFilter(request, response);
        }catch (AuthenticationException ex){
            verifyAuthenticationEntryPoint.commence(request, response, ex);
        }
    }

    private void checkJwt(String jwt) {
        if (StringUtils.isBlank(jwt)) {
            throw new InsufficientAuthenticationException(VerifyAuthenticationEntryPoint.VerifyConstants + ExceptionCodeEnum.EXPIRED_TOKEN.getDesc());
        }
    }

    private void checkJti(String jtiInfo) {
        if (StringUtils.isBlank(jtiInfo) || !jtiInfo.startsWith(BEARER_PREFIX)) {
            throw new InsufficientAuthenticationException(VerifyAuthenticationEntryPoint.VerifyConstants + ERROR_TOKEN.getDesc());
        }
    }
}

6、登录失败处理

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author :
 * @date :Created in 15:20 2022/9/10
 * @description :
 * @version: 1.0
 */
@Slf4j
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {

    private final String UserConstants = "用户";

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
        String message;
        if(e.getMessage().contains(UserConstants)){
            ServletUtils.render(request, response, Result.of(ExceptionCodeEnum.PARAM_ERROR, e.getMessage()));
            return;
        }
        if (e instanceof BadCredentialsException) {
            message = "用户名不存在或密码错误!";
        } else if (e instanceof AccountStatusException) {
            message = "用户状态不可用!";
        } else {
            message = "登录失败!";
            log.error("[登录失败]message:{}", ExceptionUtils.getStackTrace(e));
        }
        log.info("[登录失败] - {}", message);
        ServletUtils.render(request, response, Result.of(ExceptionCodeEnum.PARAM_ERROR, message));
    }
}

7、权限不足处理

mport lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author :
 * @date :Created in 16:30 2022/9/8
 * @description :
 * @version: 1.0
 */
@Slf4j
@Component
public class LoginUserAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        log.warn("用户权限不足,访问[{}]失败", request.getRequestURI());
        ServletUtils.render(request, response, Result.of(ExceptionCodeEnum.FORBIDDEN_OPERATION, "权限不足!"));
    }
}

8、校验失败处理

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author :
 * @date :Created in 16:37 2022/9/8
 * @description :
 * @version: 1.0
 */
@Component
@Slf4j
public class VerifyAuthenticationEntryPoint implements AuthenticationEntryPoint {

    public static final String VerifyConstants = "验证异常:";

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
        log.warn("用户认证,访问[{}]失败,AuthenticationException={}", request.getRequestURI(), e);
        String message;
        if (e instanceof InsufficientAuthenticationException) {
            message = "请重新登录!";
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        } else {
            message = "验证失败!";
            log.error("[验证失败]message:{}", ExceptionUtils.getStackTrace(e));
        }
        log.info("[验证失败] - {}", message);
        ServletUtils.render(request, response, Result.of(ExceptionCodeEnum.PARAM_ERROR, message));
    }
}

9、退出登录

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author :
 * @date :Created in 16:28 2022/9/8
 * @description :
 * @version: 1.0
 */
@Component
@Slf4j
@AllArgsConstructor
public class LogoutSuccessHandler implements org.springframework.security.web.authentication.logout.LogoutSuccessHandler {

    private IUserCacheService iUserCacheService;

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.info("[logout]用户退出登录");
        String header = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (header == null || !header.startsWith(BEARER_PREFIX)) {
            log.info("[logout]用户退出登录成功");
        } else {
            String jti = header.replace(BEARER_PREFIX, "");
            iUserCacheService.deleteJwtAndUser(jti, null);
        }
        log.info("[logout]用户退出登录成功");
        ServletUtils.render(request, response, Result.success(null));
    }
}

10、前后端分离工具类

import com.alibaba.fastjson.JSONObject;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author :
 * @date :Created in 16:30 2022/9/11
 * @description :
 * @version: 1.0
 */
public class ServletUtils {

    /**
     * 渲染到客户端
     *
     * @param object   待渲染的实体类,会自动转为json
     */
    public static void render(HttpServletRequest request, HttpServletResponse response, Object object) throws IOException {
        // 允许跨域
        response.setHeader("Access-Control-Allow-Origin", "*");
        // 允许自定义请求头token(允许head跨域)
        response.setHeader("Access-Control-Allow-Headers", "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
        response.setHeader("Content-type", "application/json;charset=UTF-8");
        response.getWriter().print(JSONObject.toJSONString(object));
    }


    /**
     * 获取request
     */
    public static HttpServletRequest getRequest() {
        return getRequestAttributes().getRequest();
    }

    /**
     * 获取response
     */
    public static HttpServletResponse getResponse() {
        return getRequestAttributes().getResponse();
    }

    public static ServletRequestAttributes getRequestAttributes() {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        return (ServletRequestAttributes) attributes;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring SecurityJWT(JSON Web Token)是一种常见的组合,用于实现身份验证和授权机制。 JWT是一种轻量级的身份验证和授权的解决方案,它使用JSON格式来定义安全声明。JWT通常由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。头部包含算法和令牌类型等信息,载荷包含用户身份和其他相关信息,签名用于验证令牌的完整性和真实性。 Spring Security提供了对JWT的支持,并可以与Spring Boot框架无缝集成。您可以通过以下步骤来实现Spring Security + JWT的集成: 1. 添加依赖:在项目的构建文件中,添加Spring SecurityJWT相关的依赖,如spring-boot-starter-security和jjwt。 2. 配置Spring Security:创建一个继承自WebSecurityConfigurerAdapter的配置类,并重写configure方法来配置Spring Security。在该方法中,您可以定义身份验证和授权规则。 3. 创建JWT工具类:创建一个JWT工具类,用于生成、解析和验证JWT。您可以使用jjwt库来处理JWT操作。 4. 实现用户认证:在Spring Security配置类中,您可以实现UserDetailsService接口,并重写loadUserByUsername方法来根据用户名加载用户信息。在该方法中,您可以从数据库或其他数据源中获取用户信息,并构建一个UserDetails对象返回。 5. 实现JWT过滤器:创建一个自定义的过滤器,用于解析和验证传入请求中的JWT。在该过滤器中,您可以使用JWT工具类来解析JWT,并将用户信息添加到Spring Security的上下文中。 6. 配置Spring Security过滤器链:在Spring Security配置类中,将JWT过滤器添加到过滤器链中,以确保每个请求都经过JWT验证。 通过以上步骤,您可以实现Spring SecurityJWT的集成,实现基于JWT的身份验证和授权机制。这样,您可以使用JWT生成和验证令牌,并通过Spring Security保护您的应用程序资源。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白de成长之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值