springboot 集成jwt 如何优雅的获取登录信息

springboot 集成jwt 如何优雅的获取登录信息

一、前言

通常springboot项目在实战应用中,需要处理用户权限体系,而jwt目前也是业界里主要的实现方案,本表主要为大家讲解一下,在项目如何优雅的集成jwt后并做到,优雅获取登录信息。

二、源码资料

源码资料已为大家准备好: 源码

三、实现思路

1、pom.xml集成jwt相关依赖

2、编写登录信息获取的注解 @LoginUser

3、编写非登录鉴权的注解@Login

3、实现HandlerInterceptor接口的preHandle方法判断请求的接口参数是否添加注解

4、实现HandlerMethodArgumentResolver类的resolveArgument方法

5、将实现类添加至WebMvcConfigurer的addArgumentResolvers方法

6、添加jwt工具类

7、controller层方法对入参添加注解 @Login @LoginUser

8、添加servlet拦截器AuthInterceptor


四、代码案例

1、pom.xml集成jwt相关依赖

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.2</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.2</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.2</version>
            <scope>runtime</scope>
        </dependency>

2、编写登录信息获取的注解 @LoginUser

/**
 * Copyright (c) 2018 人人开源 All rights reserved.
 *
 * https://www.softworld.vip
 *
 * 版权所有,侵权必究!
 */

package com.jiuzhou.common.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * github地址 http://www.github.com/wanyushu
 * gitee地址 http://www.gitee.com/wanyushu
 * @author yushu
 * @email 921784721@qq.com
 **/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {

}

3、编写非登录鉴权的注解@Login

/**
 * Copyright (c) 2018 人人开源 All rights reserved.
 *
 * https://www.softworld.vip
 *
 * 版权所有,侵权必究!
 */

package com.jiuzhou.common.annotation;

import java.lang.annotation.*;

/**
 * 登录效验
 * @author Mark sunlightcs@gmail.com
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Login {
}

4、实现HandlerMethodArgumentResolver类的resolveArgument方法,并判断是否需要登录信息

/**
 * Copyright (c) 2016-2019 人人开源 All rights reserved.
 *
 * https://www.renren.io
 *
 * 版权所有,侵权必究!
 */

package com.jiuzhou.common.resolver;

import com.jiuzhou.common.annotation.LoginUser;
import com.jiuzhou.common.constants.CommonConstants;
import com.jiuzhou.entity.UserInfo;
import com.jiuzhou.service.UserInfoService;
import org.apache.catalina.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

/**
 * 有@LoginUser注解的方法参数,注入当前登录用户
 */
@Component
public class LoginUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

    @Autowired
    private UserInfoService userInfoService;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().isAssignableFrom(UserInfo.class) && parameter.hasParameterAnnotation(LoginUser.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container,
                                  NativeWebRequest request, WebDataBinderFactory factory) {
        //获取用户ID
        Object object = request.getAttribute(CommonConstants.USER_KEY, RequestAttributes.SCOPE_REQUEST);
        if(object == null){
            return null;
        }

        //获取用户信息
        UserInfo userInfo = userInfoService.getById((Long)object);

        return userInfo;
    }
}

5、将实现类添加至WebMvcConfigurer的addArgumentResolvers方法

package com.jiuzhou.common.configure;

import com.jiuzhou.common.resolver.LoginUserHandlerMethodArgumentResolver;
import com.jiuzhou.intercepter.AuthInterceptor;
import com.jiuzhou.intercepter.RepeatInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
 * github地址 http://www.github.com/wanyushu
 * gitee地址 http://www.gitee.com/wanyushu
 * @author yushu
 * @email 921784721@qq.com
 **/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private RepeatInterceptor repeatInterceptor;

    @Autowired
    private AuthInterceptor authInterceptor;

    @Autowired
    private LoginUserHandlerMethodArgumentResolver loginUserHandlerMethodArgumentResolver;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(repeatInterceptor)
                .addPathPatterns("/questionGroup/**");
        registry.addInterceptor(authInterceptor).addPathPatterns("/user/**");
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(loginUserHandlerMethodArgumentResolver);
    }

}

6、添加jwt工具类

package com.jiuzhou.common.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * token过期配置
 *
 * @author Chopper
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "system.auth")
public class JwtProperties {

    /**
     * token默认过期时间
     */
    private long tokenExpireTime = 2;
    /**
     * 密钥
     */
    private String jwtKey;
}


package com.jiuzhou.common.token;

import com.alibaba.fastjson.JSON;
import com.jiuzhou.common.Enum.SecurityEnum;
import com.jiuzhou.common.Enum.UserEnums;
import com.jiuzhou.common.properties.JwtProperties;
import io.jsonwebtoken.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 */
@Component
public class TokenUtil {

    private Logger logger = LoggerFactory.getLogger(TokenUtil.class);

    @Autowired
    private JwtProperties tokenProperties;
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 构建token
     *
     * @param username 主体
     * @param claim    私有声明
     * @param longTerm 长时间特殊token 如:移动端,微信小程序等
     * @param userEnums 用户枚举
     * @return TOKEN
     */
    public Token createToken(String username, Object claim, boolean longTerm, UserEnums userEnums) {
        Token token = new Token();
        //访问token
        String accessToken = createToken(username, claim, tokenProperties.getTokenExpireTime());

        redisTemplate.opsForValue().set(CachePrefix.ACCESS_TOKEN.getPrefix(userEnums) + accessToken, 1,
                tokenProperties.getTokenExpireTime(), TimeUnit.MINUTES);
        //刷新token生成策略:如果是长时间有效的token(用于app),则默认15天有效期刷新token。如果是普通用户登录,则刷新token为普通token2倍数
        Long expireTime = longTerm ? 15 * 24 * 60L : tokenProperties.getTokenExpireTime() * 4; //设置8小时
        String refreshToken = createToken(username, claim, expireTime);

        redisTemplate.opsForValue().set(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + refreshToken, 1, expireTime, TimeUnit.MINUTES);

        token.setAccessToken(accessToken);
        token.setRefreshToken(refreshToken);
        return token;
    }

    /**
     * 获取token信息
     * @param token
     * @return
     */
    public AuthUser getTokenInfo(String token){
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(SecretKeyUtil.generalKeyByDecoders())
                    .parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) {
//            throw new ServiceException(ResultCode.USER_AUTH_EXPIRED.code()+"",ResultCode.USER_AUTH_EXPIRED.message());
        }
        if(null==claims){
            return null;
        }
        return JSON.toJavaObject(JSON.parseObject(claims.get(SecurityEnum.USER_CONTEXT.getValue()).toString()), AuthUser.class);
    }
    /**
     * 刷新token
     *
     * @param oldRefreshToken 刷新token
     * @param userEnums 用户枚举
     * @return token
     */
    public Token refreshToken(String oldRefreshToken, UserEnums userEnums) {

        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(SecretKeyUtil.generalKeyByDecoders())
                    .parseClaimsJws(oldRefreshToken).getBody();
        } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) {
            //token 过期 认证失败等
            logger.debug("用户登录信息失效!");
//            throw new ServerException(ResultCode.USER_AUTH_EXPIRED.message());
        }

        //获取存储在claims中的用户信息
        String json = claims.get(SecurityEnum.USER_CONTEXT.getValue()).toString();
        AuthUser authUser = JSON.toJavaObject(JSON.parseObject(json), AuthUser.class);


        String username = authUser.getUserName();
        //获取是否长期有效的token
        boolean longTerm = authUser.getLongTerm();
        //如果缓存中有刷新token &&
        if ( null!=redisTemplate.opsForValue().get(CachePrefix.REFRESH_TOKEN + ":" + UserEnums.MEMBER+":" + oldRefreshToken)) {
            Token token = new Token();
            //访问token
            String accessToken = createToken(username, authUser, tokenProperties.getTokenExpireTime());
            redisTemplate.opsForValue().set(CachePrefix.ACCESS_TOKEN.getPrefix(userEnums) + accessToken, 1, tokenProperties.getTokenExpireTime(), TimeUnit.MINUTES);

            //如果是信任登录设备,则刷新token长度继续延长
            Long expirationTime = tokenProperties.getTokenExpireTime() * 2;
            if (longTerm) {
                expirationTime = 60 * 24 * 15L;
            }

            //刷新token生成策略:如果是长时间有效的token(用于app),则默认15天有效期刷新token。如果是普通用户登录,则刷新token为普通token2倍数
            String refreshToken = createToken(username, authUser, expirationTime);

            redisTemplate.opsForValue().set(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + refreshToken, 1, expirationTime, TimeUnit.MINUTES);
            token.setAccessToken(accessToken);
            token.setRefreshToken(refreshToken);
            redisTemplate.delete(CachePrefix.REFRESH_TOKEN.getPrefix(userEnums) + oldRefreshToken);
            return token;
        } else {
            throw new RuntimeException(ResultCode.USER_CONNECT_LOGIN_ERROR.message());
        }

    }

    /**
     * 生成token
     *
     * @param username       主体
     * @param claim          私有神明内容
     * @param expirationTime 过期时间(分钟)
     * @return token字符串
     */
    private String createToken(String username, Object claim, Long expirationTime) {
        //JWT 生成
        return Jwts.builder()
                //jwt 私有声明
                .claim(SecurityEnum.USER_CONTEXT.getValue(), JSON.toJSONString(claim))
                //JWT的主体
                .setSubject(username)
                //失效时间 当前时间+过期分钟
                .setExpiration(new Date(System.currentTimeMillis() + expirationTime * 60 * 1000))
                //签名算法和密钥
                .signWith(SecretKeyUtil.generalKey())
                .compact();
    }
}

7、controller层方法对入参添加注解 @Login @LoginUser

package com.jiuzhou.controller;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.jiuzhou.common.Enum.UserEnums;
import com.jiuzhou.common.annotation.Login;
import com.jiuzhou.common.annotation.LoginUser;
import com.jiuzhou.common.token.AuthUser;
import com.jiuzhou.common.token.Token;
import com.jiuzhou.common.token.TokenUtil;
import com.jiuzhou.common.vo.UserVo;
import com.jiuzhou.entity.UserInfo;
import com.jiuzhou.service.UserAccountService;
import com.jiuzhou.service.UserInfoService;
import com.jiuzhou.utils.RestResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;



/**
 * github地址 http://www.github.com/wanyushu
 * gitee地址 http://www.gitee.com/wanyushu
 * @author yushu
 * @email 921784721@qq.com
 **/
@Api(tags = "【用户】信息表controller",value= "【用户】信息表相关接口")
@RestController
@RequestMapping("user")
public class UserInfoController {

    private Logger logger = LoggerFactory.getLogger(UserInfoController.class);

    @Autowired
    UserAccountService userAccountService;

    @Autowired
    private UserInfoService userInfoService;

    @Autowired
    private TokenUtil tokenUtil;

    /**
     * @return
     */

    @Login
    @ApiOperation(value = "用户登录接口")
    @PostMapping("login")
    public RestResult login(@RequestBody UserVo userVo)  {
        logger.info("用户登录:{}",userVo);
        UserInfo userInfo = userInfoService.getOne(Wrappers.<UserInfo>lambdaQuery()
                .eq(UserInfo::getEmail, userVo.getAccount()));
        AuthUser authUser = new AuthUser(null,userInfo.getUserId(),userInfo.getUserName(),userInfo.getAvatar());
        Token token = tokenUtil.createToken(userInfo.getUserName(), authUser, false, UserEnums.MEMBER);
        return RestResult.ok(token);
    }

    /**
     * @return
     */

    @ApiOperation(value = "查询用户信息")
    @GetMapping("info")
    public RestResult login(@LoginUser UserInfo userInfo)  {
        logger.info("用户获取登录信息:{}",userInfo);
        return RestResult.ok(userInfo);
    }

}

7

/**
 * Copyright (c) 2016-2019 人人开源 All rights reserved.
 *
 * https://www.renren.io
 *
 * 版权所有,侵权必究!
 */

package com.jiuzhou.intercepter;


import com.jiuzhou.common.annotation.Login;
import com.jiuzhou.common.constants.CommonConstants;
import com.jiuzhou.common.token.AuthUser;
import com.jiuzhou.common.token.TokenUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * github地址 http://www.github.com/wanyushu
 * gitee地址 http://www.gitee.com/wanyushu
 * @author yushu
 * @email 921784721@qq.com
 **/
@Component
public class AuthInterceptor extends HandlerInterceptorAdapter {


    @Autowired
    private TokenUtil tokenUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
        Login annotation;
        if(handler instanceof HandlerMethod) {
            annotation = ((HandlerMethod) handler).getMethodAnnotation(Login.class);
        }else{
            return true;
        }
        //从header中获取token
        String token = request.getHeader("token");
        //如果header中不存在token,则从参数中获取token
        if(StringUtils.isBlank(token)){
            token = request.getParameter("token");
        }

        if(null==token){
            return false;
        }
        AuthUser authUser = tokenUtil.getTokenInfo(token);
        //设置userId到request里,后续根据userId,获取用户信息
        request.setAttribute(CommonConstants.USER_KEY, authUser.getId());

        return true;
    }
}

8、添加servlet拦截器AuthInterceptor

/**
 * Copyright (c) 2016-2019 人人开源 All rights reserved.
 *
 * https://www.renren.io
 *
 * 版权所有,侵权必究!
 */

package com.jiuzhou.intercepter;


import com.jiuzhou.common.annotation.Login;
import com.jiuzhou.common.constants.CommonConstants;
import com.jiuzhou.common.token.AuthUser;
import com.jiuzhou.common.token.TokenUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * github地址 http://www.github.com/wanyushu
 * gitee地址 http://www.gitee.com/wanyushu
 * @author yushu
 * @email 921784721@qq.com
 **/
@Component
public class AuthInterceptor extends HandlerInterceptorAdapter {


    @Autowired
    private TokenUtil tokenUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
        Login annotation;
        if(handler instanceof HandlerMethod) {
            annotation = ((HandlerMethod) handler).getMethodAnnotation(Login.class);
        }else{
            return true;
        }
        //从header中获取token
        String token = request.getHeader("token");
        //如果header中不存在token,则从参数中获取token
        if(StringUtils.isBlank(token)){
            token = request.getParameter("token");
        }

        if(null==token){
            return false;
        }
        AuthUser authUser = tokenUtil.getTokenInfo(token);
        //设置userId到request里,后续根据userId,获取用户信息
        request.setAttribute(CommonConstants.USER_KEY, authUser.getId());

        return true;
    }
}

五、案例测试

1、postman发送请求用户登录接口

http://127.0.0.1:8081/user/login
post    application/json  

{"account":"921784721@qq.com","password":"123456"}

在这里插入图片描述

2、获取登录信息接口

http://127.0.0.1:8081/user/login
get 
header:{"token":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyQ29udGV4dCI6IntcImlkXCI6MSxcImxvbmdUZXJtXCI6ZmFsc2UsXCJ0ZW5hbnRJZFwiOjAsXCJ1c2VyTmFtZVwiOlwi5Luj56CB5rGf5rmWXCJ9Iiwic3ViIjoi5Luj56CB5rGf5rmWIiwiZXhwIjoxNzAzOTY2NDE3fQ.02Cj4KtnXZOeZi530hO3IakFmqhV7hP-6H749mmBdXQ"} 

{
    "code": 0,
    "msg": null,
    "data": {
        "userId": 1,
        "userAccount": "admin",
        "userName": "代码江湖",
        "avatar": null,
        "linkPhone": null,
        "email": "921784721@qq.com",
        "sex": 1,
        "realName": "九州之子",
        "status": 1,
        "createDate": null,
        "updateTime": null
    }
}

六、总结

1、在jwt获取登录信息的过程中注解@Login主要用于屏蔽登录的接口

2、@LoginUser 注解用于获取用户的信息

3、注意登录信息的获取在项目实战中,可能仅仅只需要获取userId即可,可关注我后续总结

六、获取更多springboot使用方法请前往 springboot框架使用技巧

  • 55
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于Spring Boot集成JWT实现登录的步骤,可以按照以下几个步骤进行操作: 1. 添加依赖:在`pom.xml`文件中添加以下依赖,以引入JWT相关的库: ```xml <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.2</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.2</version> <scope>runtime</scope> </dependency> dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.2</version> <scope>runtime</scope> </dependency> ``` 2. 创建JWT工具类:创建一个JWT工具类,用于生成和解析JWT。你可以使用`Jwts.builder()`来创建JWT,并设置相关的声明(例如,用户ID、角色等)和过期时间。 3. 创建登录接口:创建一个登录接口,用于验证用户名和密码,并生成相应的JWT。在验证通过后,可以使用JWT工具类生成JWT并返回给客户端。 4. 配置Spring Security:为了保护你的API,你需要进行Spring Security的配置。你可以创建一个继承`WebSecurityConfigurerAdapter`的类,并重写`configure()`方法来配置安全规则。在这个方法中,你可以配置哪些URL需要进行认证,哪些URL不需要认证(例如,登录接口)。 5. 创建Token过滤器:创建一个Token过滤器,用于验证请求中的JWT,并将用户信息存储到SecurityContext中,以便在后续的请求中使用。 6. 配置Token过滤器:在Spring Security配置类中,将Token过滤器添加到过滤器链中,以保证每个请求都会经过Token过滤器的验证。 通过以上步骤,你就可以实现Spring Boot集成JWT实现登录功能。当用户登录成功后,会生成一个JWT,并在后续的请求中使用该JWT进行身份验证。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值