springboot集成JWT实现token认证的示例和一些常见问题

springboot的拦截器无效的问题

springboot+jwt+token

1.扫描包的问题

导致拦截器失效的原因可能是没有扫描到配置拦截器的包
查看spring启动类的,添加@ComponentScan(basePackages = {"com.example.demo.config"})或@SpringBootApplication(scanBasePackages = {"com.example.demo.config"})

2.springboot版本问题

springboot2版本以上不支持WebMvcConfigurerAdapter,pom文件中可查看自己的版本。
将拦截器配置到Spring Boot环境中,有两种方法
// 方法一:实现WebMvcConfigurer接口
public class WebConfig implements WebMvcConfigurer{
	// ...
}
// 方法二:继承WebMvcConfigurationSupport类
public class WebConfig extends WebMvcConfigurationSupport{
	// ...
}

这两种方式有冲突,如果其他有可能的原因都排查了,可以看看是不是冲突问题,我的就是因为我写了一个实现方式的,结果有人在swagger里面写了一个继承的,排查了好久才看到,两边需要统一。
WebMvcConfigurer是自动配置,而WebMvcConfigurationSupport 是手动配置,当classpath中存在WebMvcConfigurationSupport 对象时,自动配置就不会生效,所以需要统一。

3.路径问题

@Override
public void addInterceptors(InterceptorRegistry registry) {   			
	registry.addInterceptor(authenticationInterceptor).addPathPatterns("/**");
			.excludePathPatterns("/","/user/login");
}      

addPathPatterns()里面的路径是"/**",不是"/*"。也可以把全局路径加上,要注意自己的项目的路径,看看配置文件是否有。/**表示当前目录以及所有子目录,/*表示当前目录,不包括子目录。
excludePathPatterns()里面是不需要拦截的路径,是"/"。

4.注解问题

配置拦截器的类上要加@Configuration注解,这样才能统一管理拦截器实例。
拦截器的类上要加@Component注解,这样当前拦截器才能被扫描到。

5.简单的示例

  1. 引入依赖
		<!--引入JWT依赖->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>

2.创建自定义注解,拦截器就可以通过注解来区分是否需要拦截,需要拦截的方法条件注解即可

/**
 * require token.
 *
 * @author Mei
 * @since 2020/8/20
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginToken {
    boolean required() default true;
}
/**
 * skip token.
 *
 * @author Mei
 * @since 2020/8/20
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SkipToken {
    boolean required() default true;
}

3.编写JwtUtil工具类

/**
 * JWT工具类.
 *
 * @author Mei
 * @since 2020/8/20
 */
@Slf4j
public class JwtUtil {
    /**
     * 密钥,不泄露,不更改
     */
    private static final String SECRET_KEY = "设置自己的密钥";
    /**
     * token过期时间(单位:豪秒)
     */
    public static final long TOKEN_EXPIRE_TIME = 24 * 60 * 60 * 1000;
    /**
     * 签发人
     */
    private static final String ISSUER = "issuer";


    /**
     * token生成器
     *
     * @param userId 用户Id
     * @param role   角色
     * @return token
     */
    private static String generateToken(final long userId,
                                        final Integer role) {
        Date now = new Date();
        // 算法
        Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
        String token =
                JWT.create()
                        .withIssuer(ISSUER)
                        .withIssuedAt(now)
                        .withExpiresAt(new Date(now.getTime() + TOKEN_EXPIRE_TIME))
                        .withClaim("role", role)
                        .withClaim("userId", userId)
                        .sign(algorithm);
        log.info("generateToken: 生成的token为 [{}]", token);
        return token;
    }


    /**
     * 生成token
     *
     * @param user 员工信息
     * @return 生成后的token
     */
    public static String generateToken(User user, int role) {
        return generateToken(
                user.getId(),
                role);
    }

    /**
     * 验证token
     *
     * @param token token
     * @return true/false
     */
    public static boolean verify(String token) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
            JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER).build();
            verifier.verify(token);
            return true;
        } catch (Exception e) {
            log.warn("verify:[{}]", e.getMessage());
            log.error(e.getMessage(), e);
            return false;
        }
    }

    /**
     * 解析token(从token获取UserId)
     *
     * @param token token
     * @return ID
     */
    public static Long getUserId(String token) {
        try {
            return JWT.decode(token).getClaim("userId").asLong();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }
    
}

4.编写拦截器

/**
 * 拦截器.
 *
 * @author Mei
 * @since 2020/8/20
 */
@Slf4j
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {

    @Resource
    private UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        log.info("preHandle:拦截 [request:{}, response:{}, handler:{}]", request, response, handler);

        // 从 http 请求头中取出 token
        String token = request.getHeader("Authorization");

        log.info("preHandle:Authorization 为[token:{}]", token);
        // 如果不是映射到方法直接通过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        //检查是否有SkipToken注释,有则跳过认证
        if (method.isAnnotationPresent(SkipToken.class)) {
            log.info("跳过认证");
            SkipToken loginToken = method.getAnnotation(SkipToken.class);
            if (loginToken.required()) {
                return true;
            }
        }
        //检查有没有需要用户权限的注解
        if (method.isAnnotationPresent(LoginToken.class)) {
            LoginToken checkToken = method.getAnnotation(LoginToken.class);
            if (checkToken.required()) {
                // 执行认证
                if (token == null) {
                    response.sendRedirect(request.getContextPath() + "/user/login");
                    return true;
                }
                // 获取 token 中的 userId
                long userId;
                try {
                    userId = JWTUtil.getUserId(token);
                } catch (JWTDecodeException j) {
                    throw new RuntimeException("访问异常!");
                }
                User user = userService.getById(userId);
                if (user == null) {
                    throw new RuntimeException("用户不存在,请重新登录");
                }
                Boolean verify = JWTUtil.verify(token);
                if (!verify) {
                    throw new RuntimeException("非法访问!");
                }
                return true;
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

5.配置拦截器

/**
 * 配置拦截器.
 *
 * @author Mei
 * @since 2020/8/21
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Resource
    private AuthenticationInterceptor authenticationInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 拦截所有请求,通过判断是否有 @SkipToken注解 决定是否需要登录
        registry.addInterceptor(authenticationInterceptor).addPathPatterns("/**");
    }
}

6.Controller

	@PostMapping(path = "/login")
    @ApiOperation(value = "登录", notes = "根据用户名和密码登录,返回token")
    @SkipToken
    public R login(HttpServletRequest request,
                   @RequestParam(value = "name") String userName,
                   @RequestParam(value = "pwd") String passWord) {
        String msg = "用户不存在";
        User user1 = new User();
        user1.setName(userName);
        User user = userService.selectOne(user1);
        if (user != null) {
            if (user.getPwd().equals(passWord)) {
                msg = "登录成功";
                String token = JWTUtil.generateToken(user, 1);
                return R.ok(msg).put("user", user).put("Authorization", token);

            } else {
                msg = "密码错误";
                return R.ok(msg);
            }
        }
        return R.error(msg);
    }

    @GetMapping(path = "/all")
    @ApiOperation(value = "查询所有用户")
    @LoginToken
    public Object all() {
        List<Map<String, Object>> list;
        list = userService.selectAll();
        return R.ok().putAll(list);
    }

7.测试
测试登录,返回token
测试登录测试查询,带上token,返回结果
在这里插入图片描述
不带token、token带错、token过期,都会拿不到数据。

6.代码改进

示例的方式可能太多地方需要添加注解了,这样会比较麻烦,一般来说,除了登录不需要token之外,其他基本都需要token,当然还是根据具体业务来。这样就可以把示例代码的拦截器的内容稍微改一下,拦截器配置类是默认拦截所有请求的("/**"),那就只需要把不需要token的请求添加注解就可以了,这样就不用把所有需要token的请求添加注解了。也可以在拦截器配置类里面添加不需要token的路径,但是如果,不需要token的路径增多,这里也会增多,所以@SkipToken注解可以保留。
拦截器类代码

@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        log.info("preHandle:拦截 [request:{}, response:{}, handler:{}]", request, response, handler);

        // 从 http 请求头中取出 token
        String token = request.getHeader("Authorization");

        log.info("preHandle:TOKEN 为[token:{}]", token);
        // 如果不是映射到方法直接通过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        //检查是否有PassToken注释,有则跳过认证
        if (method.isAnnotationPresent(SkipToken.class)) {
            log.info("跳过认证");
            SkipToken loginToken = method.getAnnotation(SkipToken.class);
            if (loginToken.required()) {
                return true;
            }
        } else if (token == null) {
            response.sendRedirect(request.getContextPath() + "/user/login");
            return true;
        } else {
            // 获取 token 中的 userId
            long userId = JwtUtil.getUserId(token);
            User user = userService.getById(userId);
            if (user == null) {
                throw new RuntimeException("用户不存在,请重新登录");
            }
            Boolean verify = JwtUtil.verify(token);
            if (!verify) {
                throw new RuntimeException("非法访问!");
            }
            return true;
        }

        return true;
    }

拦截器配置类代码

	@Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 拦截所有请求,通过判断是否有 @LoginToken注解 决定是否需要登录
        registry.addInterceptor(interceptor).addPathPatterns("/**")
                .excludePathPatterns("/**/user/login", "/error")
                .excludePathPatterns("/**/swagger-ui.html",
                        "/**/webjars/springfox-swagger-ui/**",
                        "/**/swagger-resources/**");

    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值