基于JWT规范的JWS实现token认证过程,采用JWT库jose4j,附springboot项目 demo源码下载

4 篇文章 0 订阅
4 篇文章 1 订阅

本文介绍的是单个token 的生成、认证过程,如果想要了解双token解决token刷新问题可以看下:accessToken refreshToken简单使用源码demo,双token刷新及有效时间设置

如果对JWT JWS不了解的可以看下这篇博文: JWT,JWS与JWE区别

注:本文主要为了介绍JWS实现token认证过程,其他地方写的随意,若要用到生成环境需要自行修改,当然JWS token部分可以搬过去。

本文用的JWT库是jose4j,还有其他的JWT库 参考各类JWT库的对比

废话不多说,直接上源码
demo目录结构 只增加了3个类文件,如下:

  • AccountController 测试类,有3个方法:用户注册、登录、获取用户信息
  • User 用户实体类
  • AuthorizationService 认证服务,提供3个方法:用唯一id生成keyPair , 用私钥生成token,用公钥验证token
    在这里插入图片描述
    JWT库 jose4j依赖
 <!-- https://mvnrepository.com/artifact/org.bitbucket.b_c/jose4j -->
        <dependency>
            <groupId>org.bitbucket.b_c</groupId>
            <artifactId>jose4j</artifactId>
            <version>0.6.5</version>
        </dependency>

AuthorizationService
注意:这个认证服务类中方法 createKeyPair 用来生成唯一的keyId 通过uuid 创建公钥和私钥。这个方法只要在本地运行就好了不用在线上执行。只要keyId、公钥和私钥没有被泄漏可以一直用,不用再次执行这个方法。
代码中keyId、privateKeyStr 和 publicKeyStr 是在本地运行createKeyPair 方法生成的,记录下来。

package com.example.jwt_token_demo.service;

import org.jose4j.json.JsonUtil;
import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jwk.RsaJwkGenerator;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.NumericDate;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.lang.JoseException;
import org.springframework.stereotype.Service;

import java.security.PrivateKey;
import java.util.UUID;

@Service
public class AuthorizationService {

    /**
     * keyId,公钥,私钥 都是用 createKeyPair 方法生成
     */
    private static String keyId = "fa677d525c0e4ee485a61543937794af";
    private static String privateKeyStr = "{\"kty\":\"RSA\",\"kid\":\"fa677d525c0e4ee485a61543937794af\",\"alg\":\"RS256\",\"n\":\"ki6NKW2ow53FBjWf21xNGF0v-Fzv9R4-vu5tz7LHz4vZ7TE2Lp7Xx0N2vFIH-HLZPLfAYW35W5iV29sW_MkbhVlh6f0q4AeCIeYrVjBGbcYTK5g-Sb8i9sO78DkGivryKTU4tUnOjqar2bfobwXScFhAgc4-BjIcvZ9V8LEzcAW76he500-sqekXqvYv7LbxIMlbadGEwbBqjscE83hiYjk1KSFrEeNWKP6E0X_cHVGEEGys8IKlBcfwOOCgaJ0sCFxvN3M54V33jSUknFzHAi1qJRsOI87-Fk1oYS-aniQOTfm5y5x1syTIgWEX9JvXCQgTxjp2kMItuoL2G2faoQ\",\"e\":\"AQAB\",\"d\":\"OmSUCN-AEZv9LwzOrW6CcWAQIHLne4-4WsadYOE2hcaEqAYHcboL0dI2JOXTv0AJXQK9u22VtSwPeMJcvV-MOclJnpF9xf3Z0rbByuz_xSvhToHDJ-xNCCuJ8FynK28wuptC6s_vzfXwIckf9PFrbWsjYXbEOe9cobZ7Ould9boHkEq-x0eKkDFfcQbevuQeM54FCk5FGl_D4wpbuqudZiT8FYCJPO7m-MTnND6xzdEVuApuQUPCjAin34ygp9QPk2VfQaM1z-ZHchTaXtsv5gyLwu3bqA91vugmrtGuSBG8pFaN6mYL_ivt1Q_7QDLqXZ5_WBhkoXO5DZ4-6ApLvQ\",\"p\":\"58fTzOMjU1zJ42ZdUkJjSpRlZXdlWx3ILStrvVw7kCHH1iBZ4Lckd7SdqJ9DUfdIBFTNPaAKfDqpoz7fWOUZyyUpy7_8qaDagFi53pAjuCGUuKnOV5hbSPBIOvGi9iTHwLNz6Om5eRV6lBXoTRJgT7g8u88HeSyl29RmYt-N7rs\",\"q\":\"oXTxPC0BFDxpFjk2Jpuj6hBkioD3Kiv7HEJjxRoOL3TEAAc3811f7V7bO5FWK2L_2g8I5YiZhsyh0SyxjPBfuIzcbAfGSTAsFqKiIOGZ7fqiFTOV319FJIkcMOrT2dbvGxNeGgTJpWB1vcX6C8i3ytzPFto1H9Bl3xAPVFflHFM\",\"dp\":\"RU-eaLCryav_u37K_WRY6N6Di9oudxbq24cWiuPf8_QGHGREPEzIHPvoAZrOuN4nrRPm5DzNpeStAeI1TBIGqpcMbp-U4Oz3KlZeDs4vwEpafPZafBtVgPRJxUapIs5Q5bFEQixSiIEBzPLYKuQJ5Q0FLGx2oafWWWykyYBsoy0\",\"dq\":\"SS2_yQ581rcqyi_UI1uXx5b2evBJFowonH5ayhMtKsU5sOmUqnE_8U50_2K4M6IDZMo7tg1byIUnMq-XKdIpEHSH008SyElVMk00PsMCCaL3o7Rl0YBUzmJ2rJVCwBFy_kqg9BoHazV1KDZ7RqwK4Z-DHVB5k5nZEmktCYVtCpE\",\"qi\":\"jDbVP3xGaYx2wgAGJT7KNjdoC4dYbl5ajp9saRCgAih3TYr1CjZSrLRm9L90UukgwajU0pHaffP74epVx0RBhnS5GtZhCoGitpLwYSDkZ9qTMTVnFPHrg6M1OYEEXjZ_UKlnYCrzrDe6tfHcO1UTzMzsuOgHjRAV7IS6ImfzwVw\"}";
    private static String publicKeyStr = "{\"kty\":\"RSA\",\"kid\":\"fa677d525c0e4ee485a61543937794af\",\"alg\":\"RS256\",\"n\":\"ki6NKW2ow53FBjWf21xNGF0v-Fzv9R4-vu5tz7LHz4vZ7TE2Lp7Xx0N2vFIH-HLZPLfAYW35W5iV29sW_MkbhVlh6f0q4AeCIeYrVjBGbcYTK5g-Sb8i9sO78DkGivryKTU4tUnOjqar2bfobwXScFhAgc4-BjIcvZ9V8LEzcAW76he500-sqekXqvYv7LbxIMlbadGEwbBqjscE83hiYjk1KSFrEeNWKP6E0X_cHVGEEGys8IKlBcfwOOCgaJ0sCFxvN3M54V33jSUknFzHAi1qJRsOI87-Fk1oYS-aniQOTfm5y5x1syTIgWEX9JvXCQgTxjp2kMItuoL2G2faoQ\",\"e\":\"AQAB\"}";

    public static long accessTokenExpirationTime = 60 * 60 * 24;

    //jws创建token
    public String createToken(String account) {
        try {
            //Payload
            JwtClaims claims = new JwtClaims();
            claims.setGeneratedJwtId();
            claims.setIssuedAtToNow();
            //expire time
            NumericDate date = NumericDate.now();
            date.addSeconds(accessTokenExpirationTime);
            claims.setExpirationTime(date);
            claims.setNotBeforeMinutesInThePast(1);
            claims.setSubject("YOUR_SUBJECT");
            claims.setAudience("YOUR_AUDIENCE");
            //添加自定义参数,必须是字符串类型
            claims.setClaim("account", account);

            //jws
            JsonWebSignature jws = new JsonWebSignature();
            //签名算法RS256
            jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
            jws.setKeyIdHeaderValue(keyId);
            jws.setPayload(claims.toJson());
            /*
            RsaJsonWebKey jwk = null;
            try {
                jwk = RsaJwkGenerator.generateJwk(2048);
                } catch (JoseException e) {
                    e.printStackTrace();
                }
                jwk.setKeyId(keyId); */
            //PrivateKey privateKey = jwk.getPrivateKey();
            PrivateKey privateKey = new RsaJsonWebKey(JsonUtil.parseJson(privateKeyStr)).getPrivateKey();
            jws.setKey(privateKey);

            //get token
            String idToken = jws.getCompactSerialization();
            return idToken;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * jws校验token
     *
     * @param token
     * @return 返回 用户账号
     * @throws JoseException
     */
    public String verifyToken(String token) {
        try {
            JwtConsumer consumer = new JwtConsumerBuilder()
                    .setRequireExpirationTime()
                    .setMaxFutureValidityInMinutes(5256000)
                    .setAllowedClockSkewInSeconds(30)
                    .setRequireSubject()
                    //.setExpectedIssuer("")
                    .setExpectedAudience("YOUR_AUDIENCE")
                    /*
                    RsaJsonWebKey jwk = null;
                    try {
                        jwk = RsaJwkGenerator.generateJwk(2048);
                        } catch (JoseException e) {
                            e.printStackTrace();
                        }
                        jwk.setKeyId(keyId); */
                    //.setVerificationKey(jwk.getPublicKey())
                    .setVerificationKey(new RsaJsonWebKey(JsonUtil.parseJson(publicKeyStr)).getPublicKey())
                    .build();

            JwtClaims claims = consumer.processToClaims(token);
            if (claims != null) {
                System.out.println("认证通过!");
                String account = (String) claims.getClaimValue("account");
                System.out.println("token payload携带的自定义内容:用户账号account=" + account);
                return account;
            }
        }  catch (JoseException e) {
            e.printStackTrace();
        }  catch (InvalidJwtException e) {
            e.printStackTrace();
        }catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 创建jwk keyId , 公钥 ,秘钥
     */
    public static void createKeyPair(){
        String keyId = UUID.randomUUID().toString().replaceAll("-", "");
        RsaJsonWebKey jwk = null;
        try {
            jwk = RsaJwkGenerator.generateJwk(2048);
        } catch (JoseException e) {
            e.printStackTrace();
        }
        jwk.setKeyId(keyId);
        //采用的签名算法 RS256
        jwk.setAlgorithm(AlgorithmIdentifiers.RSA_USING_SHA256);
        String publicKey = jwk.toJson(RsaJsonWebKey.OutputControlLevel.PUBLIC_ONLY);
        String privateKey = jwk.toJson(RsaJsonWebKey.OutputControlLevel.INCLUDE_PRIVATE);

        System.out.println("keyId="+keyId);
        System.out.println();
        System.out.println("公钥 publicKeyStr="+publicKey);
        System.out.println();
        System.out.println("私钥 privateKeyStr="+privateKey);
    }
    
    public static void main(String[] args){
        createKeyPair();
    }
}

User

package com.example.jwt_token_demo.entity;

public class User {
    public String account;
    public String password;
    public String lastLoginTime;

    public User(){

    }

    public User(String account, String password) {
        this.account = account;
        this.password = password;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getLastLoginTime() {
        return lastLoginTime;
    }

    public void setLastLoginTime(String lastLoginTime) {
        this.lastLoginTime = lastLoginTime;
    }

    @Override
    public String toString() {
        return "User{" +
                "account='" + account + '\'' +
                ", lastLoginTime='" + lastLoginTime + '\'' +
                '}';
    }
}

AccountController
注意: 每次重启项目测试都应该 从注册开始执行,因为懒没有些持久化,仅仅在本地map保存了用户信息 ,重启项目就没了。

package com.example.jwt_token_demo.Controller;

import com.example.jwt_token_demo.entity.User;
import com.example.jwt_token_demo.service.AuthorizationService;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;
import java.util.HashMap;

@RestController
public class AccountController {

    @Autowired
    AuthorizationService authorizationService;

    //用一个hashmap存储注册用户,演示用
    private static HashMap<String,User> accountUserMap = new HashMap<>();

    //注册
    @GetMapping("/register")
    public String register(@RequestParam(name = "account") String account,
                           @RequestParam(name = "password") String password){
        JSONObject ret = new JSONObject();
        //简单校验下非空
        if(account == null || account.equals("") || password == null || password.equals("")){
            ret.put("code","1");
            ret.put("desc","account | password can not empty");
            return ret.toString();
        }

        //注册成功
        accountUserMap.put(account,new User(account,password));

        ret.put("code","0");
        ret.put("desc","ok");
        return ret.toString();
    }

    //登录
    @GetMapping("/login")
    public String login(@RequestParam(name = "account") String account,
                           @RequestParam(name = "password") String password){
        JSONObject ret = new JSONObject();
        User user = accountUserMap.get(account);
        if(user != null && password != null && password.equals(user.getPassword())){
            //登录成功
            //创建token
            String token = authorizationService.createToken(account);

            //更新用户最近登录时间
            user.setLastLoginTime(new Date().toLocaleString());
            accountUserMap.put(account,user);

            ret.put("code","0");
            ret.put("desc","ok");
            ret.put("token",token);
            return ret.toString();
        }
        ret.put("code","1");
        ret.put("desc","failed");
        return ret.toString();
    }

    //查询用户信息
    @GetMapping("/user")
    public String queryUser(@RequestParam(name = "token") String token){
        JSONObject ret = new JSONObject();
        //识别token
        try {
            String account = authorizationService.verifyToken(token);
            if(account != null) {
                System.out.println();
                ret.put("code","0");
                ret.put("desc","ok" );
                ret.put("user",accountUserMap.get(account).toString());
                return ret.toString();
            }else {
                //正常情况,token认证失败基本都是token过期,其他错误信息在上线前就调试正常了
                //token刷新问题,后面博文再做介绍
                ret.put("code","2");
                ret.put("desc","token认证失败,请刷新token");
                return ret.toString();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        ret.put("code","1");
        ret.put("desc","failed");
        return ret.toString();
    }
}

关于生成 keyId 、privateKeyStr 和 publicKeyStr, 直接在本地运行 认证服务类即可,如下图:
在这里插入图片描述

测试

注意:我提供的代码里面web服务端口是8097,你们如果没有设置端口默认8080
每次重启项目测试都应该 从注册开始执行 ,因为用户信息没做持久化,重启了就没了。

1 注册 一个账号 zhangsan/123456
浏览器输入 http://127.0.0.1:8097/register?account=zhangsan&password=123456
在这里插入图片描述
2 登录, 登录成功会返回token
浏览器输入 http://127.0.0.1:8097/login?account=zhangsan&password=123456
在这里插入图片描述

3 获取用户信息 登录的用户获取自己的信息,需要验证token

浏览器输入 127.0.0.1:8097/user?token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImN…
注意:用自己刚才登录成功返回的token
在这里插入图片描述

源码

链接:https://pan.baidu.com/s/1R9MnBhNgOJN-N66Pk4S8aA
提取码:epon

JWT调试工具 可以看下我的这篇博文: JWT token调试工具,JWT Debugger

下一章介绍token刷新问题

  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Spring Boot中,可以使用以下步骤将JWT集成到应用程序中以实现token认证: 1. 添加依赖项 在pom.xml文件中添加以下依赖项: ``` <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> ``` 2. 创建Token工具类 创建一个JwtTokenUtil工具类,该类将用于生成和验证JWT令牌。以下是一个基本的JwtTokenUtil类: ```java public class JwtTokenUtil { private static final String SECRET_KEY = "secret"; public static String generateToken(String username) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + 3600000); return Jwts.builder() .setSubject(username) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); } public static String getUsernameFromToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody() .getSubject(); } public static boolean validateToken(String token) { try { Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token); return true; } catch (SignatureException ex) { System.out.println("Invalid JWT signature"); } catch (MalformedJwtException ex) { System.out.println("Invalid JWT token"); } catch (ExpiredJwtException ex) { System.out.println("Expired JWT token"); } catch (UnsupportedJwtException ex) { System.out.println("Unsupported JWT token"); } catch (IllegalArgumentException ex) { System.out.println("JWT claims string is empty."); } return false; } } ``` 3. 创建安全配置类 创建一个SecurityConfig类,该类将用于配置Spring Security以使用JWT进行认证。以下是一个基本的SecurityConfig类: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Autowired private JwtUserDetailsService jwtUserDetailsService; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder()); } @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests().antMatchers("/authenticate").permitAll() .anyRequest().authenticated().and() .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } } ``` 4. 创建用户详细信息服务类 创建一个JwtUserDetailsService类,该类将用于从数据或其他存储中获取用户信息以进行身份验证。以下是一个基本的JwtUserDetailsService类: ```java @Service public class JwtUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 在这里获取用户信息并返回UserDetails对象 return null; } } ``` 5. 创建身份验证过滤器 创建一个JwtAuthenticationFilter类,该类将用于拦截所有请求,并进行JWT身份验证。以下是一个基本的JwtAuthenticationFilter类: ```java public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtUserDetailsService jwtUserDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { final String requestTokenHeader = request.getHeader("Authorization"); String username = null; String jwtToken = null; // JWT Token is in the form "Bearer token". Remove Bearer word and get only the Token if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) { jwtToken = requestTokenHeader.substring(7); try { username = jwtTokenUtil.getUsernameFromToken(jwtToken); } catch (IllegalArgumentException e) { System.out.println("Unable to get JWT Token"); } catch (ExpiredJwtException e) { System.out.println("JWT Token has expired"); } } else { logger.warn("JWT Token does not begin with Bearer String"); } // Once we get the token validate it. if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username); // if token is valid configure Spring Security to manually set authentication if (jwtTokenUtil.validateToken(jwtToken)) { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); usernamePasswordAuthenticationToken .setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); // After setting the Authentication in the context, we specify // that the current user is authenticated. So it passes the // Spring Security Configurations successfully. SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } } chain.doFilter(request, response); } } ``` 6. 创建身份验证控制器 创建一个JwtAuthenticationController类,该类将用于处理身份验证请求,生成JWT令牌并返回给客户端。以下是一个基本的JwtAuthenticationController类: ```java @RestController public class JwtAuthenticationController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private JwtUserDetailsService jwtUserDetailsService; @RequestMapping(value = "/authenticate", method = RequestMethod.POST) public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception { authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword()); final UserDetails userDetails = jwtUserDetailsService .loadUserByUsername(authenticationRequest.getUsername()); final String token = jwtTokenUtil.generateToken(userDetails.getUsername()); return ResponseEntity.ok(new JwtResponse(token)); } private void authenticate(String username, String password) throws Exception { try { authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); } catch (DisabledException e) { throw new Exception("USER_DISABLED", e); } catch (BadCredentialsException e) { throw new Exception("INVALID_CREDENTIALS", e); } } } ``` 现在,您的Spring Boot应用程序已经集成了JWT以进行身份验证。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值