栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring Security(7) jwt整合

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring Security(7) jwt整合

jwt 基本介绍

jwt 全称是jsonWebToken, 简单的说就是一种能够携带信息的token。
在传统的web环境中,浏览器和后端通过记录在浏览器的cookie 和存储在服务端的session 来实现登录状态,而cookie session的方式在多分布式环境下可能带来session复制,跨域访问,单点登录等问题;
直接使用后端生成token的方式,服务端也需要存储生成的token信息,因为token是无意义的。而使用jwt ,能够携带一些必要得信息比如用户id 和用户名称等;
后端就不需要对生成的token做存储,同时jwt也有时间的有效期。能够做到请求接口无状态;

缺点:

    安全性,payload是使用base64编码的,并没有加密,因此jwt中不能存储敏感数据。而session的信息是存在服务端的,相对来说更安全。无法废弃,只能等待过期失效,或增加其他的黑名单类似的逻辑处理失效。

jwt 官网: https://jwt.io/

格式

在使用过程中是一个base64编码的字符串

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

此字符串通过逗号分割是由3部分组成

第一部分是 header 区域,只要表示当前签名的加密方式;
第二部分是 plaoyload 区域,存储了当前的token携带的信息,包含颁发给谁,有效期等
第三部分是 将前2部分通过加密生成的,主要用于服务端校验token的合法性;

使用 基本依赖

引用对应的依赖,关于jwt的工具类有很多,这里使用 https://github.com/jwtk/jjwt

引入maven依赖


    io.jsonwebtoken
    jjwt-impl
    0.11.2
    runtime



创建token使用
		//设置自定义header 信息
        JwtBuilder jwtBuilder = Jwts.builder().setHeaderParam("a", "b");

        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.MINUTE,30);

        Date expire = instance.getTime();

        //设置playload信息
        jwtBuilder = jwtBuilder.setIssuer("me")  //谁颁发的
                .setSubject("Bob")    // token的主体是什么 ,是关于什么的
                .setAudience("you")   // 给谁的
                .setExpiration(expire) //失效时间
                .setNotBefore(new Date()) //不能在此时间之前获取
                .setIssuedAt(new Date()) //签发时间
                .setId(UUID.randomUUID().toString());//id

        //设置自定义的playload信息
        jwtBuilder.claim("key","value");

        //构建签名算法,更多签名算法查看 SignatureAlgorithm
        Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
        jwtBuilder = jwtBuilder.signWith(key);

          //执行压缩 使生成的字符串变小
        jwtBuilder = jwtBuilder.compressWith(CompressionCodecs.DEFLATE);

        System.out.println(jwtBuilder.compact());


解析读取token
        JwtParserBuilder jwtParserBuilder = Jwts.parserBuilder();
        //设置解析的签名算法
        jwtParserBuilder = jwtParserBuilder.setSigningKey(key);

        Jws claimsJws = jwtParserBuilder.build().parseClaimsJws(jwtStr);

        String signature = claimsJws.getSignature();
        System.out.println("<========>");
        System.out.println(signature);
        JwsHeader header = claimsJws.getHeader();
        System.out.println(header);
        Claims body = claimsJws.getBody();
        System.out.println(body);


jackjson 的支持

引入依赖



    io.jsonwebtoken
    jjwt-jackson 
0.11.2
runtime


//设置序列化方式
jwtBuilder = jwtBuilder.serializeToJsonWith(new JacksonSerializer());

//设置反序列化方式
jwtParserBuilder = jwtParserBuilder.deserializeJsonWith(new JacksonDeserializer());

springsecurity 整合jwt

首先回忆一下springsecurity 的默认登录和鉴权流程;

springsecurity 中主要由一整套过滤器链来处理,不同的过滤器处理不同的功能;

ChannelProcessingFilter,因为它可能需要重定向到不同的协议SecurityContextPersistenceFilter,因此可以在web请求开头的SecurityContextHolder中设置SecurityContext,并且SecurityContext的任何更改都可以复制到HttpSession当web请求结束时(准备好与下一个web请求一起使用)ConcurrentSessionFilter,因为它使用SecurityContextHolder功能并需要更新SessionRegistry以反映来自校长的持续请求身份验证处理机制 - UsernamePasswordAuthenticationFilter,CasAuthenticationFilter,BasicAuthenticationFilter等 - 以便SecurityContextHolder可以修改为包含有效的Authentication请求令牌SecurityContextHolderAwareRequestFilter,如果您使用它将Spring Security识别HttpServletRequestWrapper安装到您的servlet容器中JaasApiIntegrationFilter,如果SecurityContextHolder位于SecurityContextHolder,则会将FilterChain视为JaasAuthenticationToken中的SubjectRememberMeAuthenticationFilter,如果没有早期的身份验证处理机制更新SecurityContextHolder,并且请求提供了一个启用记住我服务的cookie,则会在那里放置一个合适的记忆Authentication对象AnonymousAuthenticationFilter,如果没有早期的身份验证处理机制更新SecurityContextHolder,那么匿名Authentication对象将被放置在那里ExceptionTranslationFilter,捕获任何Spring Security异常,以便可以返回HTTP错误响应或启动适当的AuthenticationEntryPointFilterSecurityInterceptor,用于保护web URI并在访问被拒绝时引发异常

对于一个具有session登录的流程的过滤器链执行顺序是;

当发起登录请求的时候:

UsernamePasswordAuthenticationFilter 处理登录请求的参数和处理 将登录的信息放入session中;同时将Authentication 放入SecurityContext 中;

再次发起请求时 由 SecurityContextPersistenceFilter 从请求中获取请求的session信息获取Authentication 用于后续的流程校验;

整合基本要做的有

    禁用session,不需要做任何session相关的处理。未登录的时候能返回一个json提示,而不是登录页面。登录成功后以json的返回成功和失败信息和jwt字符串后续请求在header中卸载jwt 需要通过一个自定义的filter 从header中解析出来任何设置到 SecurityContext 中;
jwt 工具类
    
            io.jsonwebtoken
            jjwt-api
            0.11.2
        
        
            io.jsonwebtoken
            jjwt-impl
            0.11.2
            runtime
        
        
            io.jsonwebtoken
            jjwt-jackson 
            0.11.2
            compile
        
//提供了2个方法都是从UserDetails 中生成信息和获取信息
public class JwtUtil {

    private static Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);

    private static String ISS_USER = "web";

    private static String SUBJECT = "auth";



    
    public static String createJwt(UserDetails userDetails){
        JwtBuilder jwtBuilder = Jwts.builder();

        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.MINUTE,30);
        Date expire = instance.getTime();

        jwtBuilder = jwtBuilder.setIssuer(ISS_USER)
                .setSubject(SUBJECT)
                .setAudience(userDetails.getUsername())
                .setExpiration(expire)
                .setNotBefore(new Date())
                .setIssuedAt(new Date())
                .setId(userDetails.getUsername());

        JSonObject jsonObject = new JSonObject();
        jsonObject.putOpt("roleCodes",((MyUserDetails)userDetails).getRoleCodes());
        jsonObject.putOpt("permissionCodes",((MyUserDetails)userDetails).getPermissionCodes());
        jwtBuilder.addClaims(jsonObject);

        jwtBuilder = jwtBuilder.signWith(key);
        //执行压缩 使生成的字符串变小
        jwtBuilder = jwtBuilder.compressWith(CompressionCodecs.DEFLATE);
        jwtBuilder = jwtBuilder.serializeToJsonWith(new JacksonSerializer());
        return jwtBuilder.compact();
    }


    
    public static UserDetails parseJwt(String jwtStr){
        JwtParserBuilder jwtParserBuilder = Jwts.parserBuilder();
        jwtParserBuilder = jwtParserBuilder.setSigningKey(key);
        jwtParserBuilder = jwtParserBuilder.deserializeJsonWith(new JacksonDeserializer());
        Jws claimsJws = jwtParserBuilder.build().parseClaimsJws(jwtStr);

        MyUserDetails userDetails = new MyUserDetails();
        Claims claims = claimsJws.getBody();
        userDetails.setUserName(claims.getId());
        userDetails.setRoleCodes((List)claims.get("roleCodes"));
        userDetails.setPermissionCodes((List)claims.get("permissionCodes"));
        return userDetails;
    }

}
配置无权限的json返回
 @Bean
    public AuthenticationEntryPoint authenticationEntryPoint(){
        return new AuthenticationEntryPoint() {
            @Override
            public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                JSonObject jsonObject = new JSonObject();
                jsonObject.putOpt("code","4001");
                jsonObject.putOpt("message","未登录");
                writeJson(response,jsonObject);
            }
        };
    }
    
     private void writeJson(HttpServletResponse response, JSonObject jsonObject){
        try {
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/json; charset=utf-8");
            PrintWriter writer = response.getWriter();
            writer.write(jsonObject.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    
    
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated().and().
        //配置exceptionHandling
        exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
处理登录成功或失败的处理

在failureHandler 中处理登录错误的信息;

successHandler 中返回登录成功和jwt字符串信息;

sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 禁用session

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated().and().
        exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
                .and()
                .formLogin().loginProcessingUrl("/loginDo").failureHandler(new AuthenticationFailureHandler(){
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
                        JSonObject jsonObject = new JSonObject();
                        jsonObject.putOpt("code","4002");
                        jsonObject.putOpt("message","登录错误");
                        writeJson(response,jsonObject);
                    }
                }).successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        JSonObject jsonObject = new JSonObject();
                        jsonObject.putOpt("code","2000");
                        jsonObject.putOpt("message","登录成功");
                        //生成token
                        Object getPrincipalObj = authentication.getPrincipal();
                        if(getPrincipalObj instanceof MyUserDetails){
                            MyUserDetails details = (MyUserDetails)getPrincipalObj;
                            String jwt = JwtUtil.createJwt(details);
                            jsonObject.putOpt("token",jwt);
                        }
                        writeJson(response,jsonObject);
                    }
                }).permitAll().and()
                .csrf().disable()
               .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().addFilterBefore(new TokenAuthFilter(authenticationManager(),authenticationEntryPoint()),UsernamePasswordAuthenticationFilter.class).httpBasic();
    }


添加 自定义解析jwtFilter

配置添加自定义的filter ,添加到 UsernamePasswordAuthenticationFilter 之前;

此filter主要逻辑是从header中解析 jwt 信息,如果能获取到就封装成UsernamePasswordAuthenticationToken 并设置到SecurityContext 中去.

public class TokenAuthFilter extends BasicAuthenticationFilter  {


    private AuthenticationEntryPoint authenticationEntryPoint;


    public TokenAuthFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    public TokenAuthFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
        super(authenticationManager, authenticationEntryPoint);
    }


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            UsernamePasswordAuthenticationToken authRequest = getUsernamePasswordAuthenticationToken(request);
            if (authRequest == null) {
                this.logger.trace("Did not process authentication request since failed to find "
                        + "username and password in Basic Authorization header");
                chain.doFilter(request, response);
                return;
            }
            String username = authRequest.getName();
            this.logger.trace(LogMessage.format("Found username '%s' in Basic Authorization header", username));
            if (authenticationIsRequired(username)) {
                SecurityContextHolder.getContext().setAuthentication(authRequest);
            }
        } catch (AuthenticationException ex) {
            SecurityContextHolder.clearContext();
            this.logger.debug("Failed to process authentication request", ex);
            onUnsuccessfulAuthentication(request, response, ex);
            this.authenticationEntryPoint.commence(request, response, ex);
            return;
        }
        chain.doFilter(request, response);
    }



    private boolean authenticationIsRequired(String username) {
       
        Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
        if (existingAuth == null || !existingAuth.isAuthenticated()) {
            return true;
        }
       
        if (existingAuth instanceof UsernamePasswordAuthenticationToken && !existingAuth.getName().equals(username)) {
            return true;
        }
      
        return (existingAuth instanceof AnonymousAuthenticationToken);
    }




    private UsernamePasswordAuthenticationToken getUsernamePasswordAuthenticationToken(HttpServletRequest request) {
        String header = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (header == null) {
            return null;
        }
        header = header.trim();
        try{
            UserDetails userDetails = JwtUtil.parseJwt(header);
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails.getUsername(),
                    userDetails.getPassword(),userDetails.getAuthorities());
            return token;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
测试

未携带jwt 或jwt错误返回

{
    "code": "4001",
    "message": "未登录"
}

执行登录返回jwt信息;

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/786251.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号