Spring boot 入门教程-token验证

 

这篇博客是在 Spring boot 入门教程-全局异常处理及日志输出  的基础上完成的。

我们在做项目时比如商城项目,有的页面的打开是需要登陆的而有的页面则不需要,所以这里就需要一种验证是否登录,或者登录是否过期,这里说一种token令牌+拦截器的方式。

生成token 使用JWT。

1.引入

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

2.生成token令牌的工具类

public class TokenUtils {

    /**
     * 签名秘钥
     */
    public static final String SECRET = "admin";

    /**
     * 生成token
     *
     * @param id 一般传入userName
     * @return
     */
    public static String createJwtToken(String id) {
        String issuer = "www.xxxx.com";
        String subject = "xxxx@126.com";
        long ttlMillis = 3600000;
        return createJwtToken(id, issuer, subject, ttlMillis);
    }

    /**
     * 生成Token
     *
     * @param id        编号
     * @param issuer    该JWT的签发者,是否使用是可选的
     * @param subject   该JWT所面向的用户,是否使用是可选的;
     * @param ttlMillis 签发时间 (有效时间,过期会报错)
     * @return token String
     */
    public static String createJwtToken(String id, String issuer, String subject, long ttlMillis) {

        // 签名算法 ,将对token进行签名
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成签发时间
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        // 通过秘钥签名JWT
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET);
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

        // Let's set the JWT Claims
        JwtBuilder builder = Jwts.builder().setId(id)
                .setIssuedAt(now)
                .setSubject(subject)
                .setIssuer(issuer)
                .signWith(signatureAlgorithm, signingKey);

        // if it has been specified, let's add the expiration
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            builder.setExpiration(exp);
        }

        // Builds the JWT and serializes it to a compact, URL-safe string
        return builder.compact();

    }

    // Sample method to validate and read the JWT
    public static Claims parseJWT(String jwt) {
        // This line will throw an exception if it is not a signed JWS (as expected)
        Claims claims = Jwts.parser()
                .setSigningKey(DatatypeConverter.parseBase64Binary(SECRET))
                .parseClaimsJws(jwt).getBody();
        return claims;
    }

    public static void main(String[] args) {
        System.out.println(TokenUtils.createJwtToken("11111"));
    }
}

3.注入当前登录用户注解

/**
 * @BelongsProject: JDTaste
 * @BelongsPackage: com.jdtaste.common.util
 * @Author: 
 * @CreateTime: 2018-07-04 15:39
 * @Description: 在Controller的方法参数中使用此注解,该方法在映射时会注入当前登录的User对象
 */
@Target(ElementType.PARAMETER)          // 可用在方法的参数上
@Retention(RetentionPolicy.RUNTIME)     // 运行时有效
public @interface CurrentUser {
}

4.需要登录的标记注解

/**
 * @BelongsProject: JDTaste
 * @BelongsPackage: com.jdtaste.common.util
 * @Author: 
 * @CreateTime: 2018-07-04 15:38
 * @Description: 在需要登录验证的Controller的方法上使用此注解
 */
@Target({ElementType.METHOD})// 可用在方法名上
@Retention(RetentionPolicy.RUNTIME)// 运行时有效
public @interface LoginRequired {
}

5.编辑拦截器

/**
 * @BelongsProject: 
 * @BelongsPackage: com.jdtaste.jdtastesso.web.intercepter
 * @Author: 
 * @CreateTime: 2018-07-04 09:50
 * @Description: 拦截器
 */
public class AuthenticationInterceptor implements HandlerInterceptor {
    public final static String ACCESS_TOKEN = "accessToken";
    @Resource
    IUserBaseService userBaseService;

    // 在业务处理器处理请求之前被调用
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 如果不是映射到方法直接通过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        // 判断接口是否需要登录
        LoginRequired methodAnnotation = method.getAnnotation(LoginRequired.class);

        // 有 @LoginRequired 注解,需要认证
        if (methodAnnotation != null) {
            // 判断是否存在令牌信息,如果存在,则允许登录
            String accessToken = request.getHeader("Authorization");


            if (null == accessToken) {
                throw new CommonException(401, "无token,请重新登录");
            } else {
                // 从Redis 中查看 token 是否过期
                Claims claims;
                try{
                     claims = TokenUtils.parseJWT(accessToken);
                }catch (ExpiredJwtException e){
                    response.setStatus(401);
                    throw new CommonException(401, "token失效,请重新登录");
                }catch (SignatureException se){
                    response.setStatus(401);
                    throw new CommonException(401, "token令牌错误");
                }

                String userName = claims.getId();
                UserBase user = userBaseService.findUserByAccount(userName);
                if (user == null) {
                    response.setStatus(401);
                    throw new CommonException(401, "用户不存在,请重新登录");
                }
                // 当前登录用户@CurrentUser
                request.setAttribute(CurrentUserConstants.CURRENT_USER, user);
                return true;
            }

        } else {//不需要登录可请求
            return true;
        }
    }
    // 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    // 在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
    }
}

 

/**
 * @BelongsProject: 
 * @BelongsPackage: com.jdtaste.jdtastesso.web.intercepter.auth
 * @Author: 
 * @CreateTime: 2018-07-04 15:45
 * @Description: 当前用户
 */
public class CurrentUserConstants {
    /**
     * 当前用户参数名
     */
    public final static String CURRENT_USER = "CurrentUser";
}

 

6.自定义参数解析器

/**
 *  
 * @BelongsPackage: com.jdtaste.jdtastesso.web.intercepter.auth
 * @Author: 
 * @CreateTime: 2018-07-04 15:42
 * @Description: 自定义参数解析器
 * 增加方法注入,将含有 @CurrentUser 注解的方法参数注入当前登录用户
 */
public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver {
    /*
     * supportsParameter:用于判定是否需要处理该参数分解,返回true为需要,并会去调用下面的方法resolveArgument。
     *resolveArgument:真正用于处理参数分解的方法,返回的Object就是controller方法上的形参对象。
     *
     * */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        System.out.println("----------supportsParameter-----------" + parameter.getParameterType());
        return parameter.getParameterType().isAssignableFrom(UserBase.class)//判断是否能转成UserBase 类型
                && parameter.hasParameterAnnotation(CurrentUser.class);//是否有CurrentUser注解
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        System.out.println("--------------resolveArgument-------------" + parameter);
        UserBase user = (UserBase) webRequest.getAttribute(CurrentUserConstants.CURRENT_USER, RequestAttributes.SCOPE_REQUEST);
        if (user != null) {
            return user;
        }
        throw new MissingServletRequestPartException(CurrentUserConstants.CURRENT_USER);
    }
}

7.将拦截器和参数解析器加入容器

/**
 * @BelongsProject: 
 * @BelongsPackage: com.jdtaste.jdtastesso.conf
 * @Author: 
 * @CreateTime: 2018-07-04 10:03
 * @Description: 配置URLInterceptor拦截器,以及拦截路径
 */
@EnableWebMvc
@Configuration
public class MyWebAppConfigurer extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // addPathPatterns 用于添加拦截规则
        // excludePathPatterns 用户排除拦截
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/*/*");
        super.addInterceptors(registry);
    }

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

    @Bean
    public CurrentUserMethodArgumentResolver currentUserMethodArgumentResolver() {
        return new CurrentUserMethodArgumentResolver();
    }

    /**
     * 解决 拦截器中注入bean 失败情况出现
     * addArgumentResolvers方法中 添加
     *  argumentResolvers.add(currentUserMethodArgumentResolver());
     */
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
}
 argumentResolvers.add(currentUserMethodArgumentResolver());
        super.addArgumentResolvers(argumentResolvers);
    }

    @Bean
    public CurrentUserMethodArgumentResolver currentUserMethodArgumentResolver() {
        return new CurrentUserMethodArgumentResolver();
    }

    /**
     * 解决 拦截器中注入bean 失败情况出现
     * addArgumentResolvers方法中 添加
     *  argumentResolvers.add(currentUserMethodArgumentResolver());
     */
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
}

8.Controller

    @LoginRequired
    @RequestMapping(value = "/token")
    public String token(@CurrentUser UserBase userBase,String account,String token) {

        log.info(account+"----"+token);
        log.info("----"+userBase.getAccount());
        log.info("params==" + userBase.toString());
        if (userBaseService.findUserByAccount(userBase.getAccount()) == null) {
            return "账号不存在";
        } else {
            UserBase result = null;
            result = userBaseService.login(userBase);
            //生成token
            //String accessToken=TokenUtils.createJwtToken(userBase.getAccount());
            if (result == null) {
                return  "密码错误";
            } else {

                return "SUCCESS";
            }
        }
    }
@LoginRequired
    @RequestMapping(value = "/token")
    public String token(@CurrentUser UserBase userBase,String account,String token) {

        log.info(account+"----"+token);
        log.info("----"+userBase.getAccount());
        log.info("params==" + userBase.toString());
        if (userBaseService.findUserByAccount(userBase.getAccount()) == null) {
            return "账号不存在";
        } else {
            UserBase result = null;
            result = userBaseService.login(userBase);
            //生成token
            //String accessToken=TokenUtils.createJwtToken(userBase.getAccount());
            if (result == null) {
                return  "密码错误";
            } else {

                return "SUCCESS";
            }
        }
    }

 

9.前端发送请求 (axios)

_this.$http.post('/api/user/login',
          this.login,
          {
            headers: {
              Authorization: JSON.parse(sessionStorage.getItem("loginUser")).accessToken,
            }
          }).then((res) => {
          console.log(res);
        });

10 .请求过程 

拦截器-->参数解析器-->controler-->拦截器

注意:参数解析器当第一个返回true 才执行第二个方法

  • 10
    点赞
  • 81
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值