SpringBoot拦截器和自定义注解验证是否登录

SpringBoot拦截器和自定义注解验证是否登录

demo地址:https://github.com/luomouren/sbDemo
参考网址:https://www.jianshu.com/p/97362fdf039e (非常感谢作者~)
用SpringBoot开发后台用于移动端API接口,移动端登录一次后,返回accessToken字符串,移动端后面的交互只要传入accessToken即可验证其有效性,将accessToken转换成CurrentUser,在controller内可以直接使用操作其bean。需要做以下几点:

  • 1.Token生成帮助类
  • 2.拦截器验证是否是有效的accessToken
  • 3.添加自定义注解LoginRequired(请求方法是否需要验证accessToken)
  • 4.添加自定义注解CurrentUser(将accessToken转换成CurrentUser)
  • 5.SpringBoot配置拦截器
  • 6.测试

1.Token生成帮助类

package com.xxx.demo.frame.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;

/**
 * token生成
 *  @author luomouren
 */
public class TokenUtils {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    /**
     * 签名秘钥
     */
    public static final String SECRET = "guangYuan";

    /**
     * 生成token
     * @param id 一般传入userName
     * @return
     */
    public static String createJwtToken(String id){
        String issuer = "www.guangyuanbj.com";
        String subject = "[email protected]";
        long ttlMillis = System.currentTimeMillis(); 
        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("admin"));
    }
}

pom.xml添加:

JSON Web Token Support For The JVM
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>

2.拦截器验证是否是有效的accessToken

package com.xxx.demo.frame.init;


import com.xxx.demo.frame.annotation.LoginRequired;
import com.xxx.demo.frame.constants.CurrentUserConstants;
import com.xxx.demo.frame.util.TokenUtils;
import com.xxx.demo.models.sys.SysUser;
import com.xxx.demo.services.user.SysUserService;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * @description:Token验证过滤器,判断是否已登录
 * @author:@luomouren.
 * @Date:2017-12-10 22:40
 */
public class AuthenticationInterceptor implements HandlerInterceptor {
    public final static String ACCESS_TOKEN = "accessToken";
    @Autowired
    private SysUserService userService;

    /**
     * 在请求处理之前进行调用(Controller方法调用之前)
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @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.getParameter(ACCESS_TOKEN);
            if (null == accessToken) {
                throw new RuntimeException("无token,请重新登录");
            }
            Claims claims = TokenUtils.parseJWT(accessToken);
            String userName = claims.getId();
            SysUser user = userService.findByUserName(userName);
            if (user == null) {
                throw new RuntimeException("用户不存在,请重新登录");
            }
            // 当前登录用户@CurrentUser
            request.setAttribute(CurrentUserConstants.CURRENT_USER, user);
            return true;
        }
        return false;
    }

    /**
     * 请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)
     *
     * @param httpServletRequest
     * @param httpServletResponse
     * @param o
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

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

    }

}

3.添加自定义注解LoginRequired(请求方法是否需要验证accessToken)

package com.xxx.demo.frame.annotation;

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

/**
 * 在需要登录验证的Controller的方法上使用此注解
 */
@Target({ElementType.METHOD})// 可用在方法名上
@Retention(RetentionPolicy.RUNTIME)// 运行时有效
public @interface LoginRequired {
}

4.添加自定义注解CurrentUser(将accessToken转换成CurrentUser)

package com.xxx.demo.frame.annotation;

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

/**
 * 在Controller的方法参数中使用此注解,该方法在映射时会注入当前登录的User对象
 */
@Target(ElementType.PARAMETER)          // 可用在方法的参数上
@Retention(RetentionPolicy.RUNTIME)     // 运行时有效
public @interface CurrentUser {
}   

自定义解析器
package com.xxx.demo.frame.init;

import com.xxx.demo.frame.annotation.CurrentUser;
import com.xxx.demo.frame.constants.CurrentUserConstants;
import com.xxx.demo.models.sys.SysUser;
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;
import org.springframework.web.multipart.support.MissingServletRequestPartException;

/**
 * @description:自定义解析器实现参数绑定
 * 增加方法注入,将含有 @CurrentUser 注解的方法参数注入当前登录用户
 * @author:@luomouren.
 * @Date:2017-12-17 21:43
 */
public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().isAssignableFrom(SysUser.class)
            && parameter.hasParameterAnnotation(CurrentUser.class);
    }

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



package com.xxx.demo.frame.constants;

/**
 * @description:当前用户
 * @author:@luomouren.
 * @Date:2017-12-17 22:10
 */
public class CurrentUserConstants {
    /**
     * 当前用户参数名
     */
    public final static String CURRENT_USER = "CurrentUser";
}

5.SpringBoot配置拦截器

继承 WebMvcConfigurerAdapter 类,Override 相应的方法
package com.xxx.demo.frame.init;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.util.List;

/**
 * @author:@luomouren
 * @Description:添加拦截器
 * @Date: 2017-12-23 16:35
 */
@Configuration
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {

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

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

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(fastJsonHttpMessageConverterEx());
        super.configureMessageConverters(converters);
    }

    @Bean
    public FastJsonHttpMessageConverterEx fastJsonHttpMessageConverterEx() {
        return new FastJsonHttpMessageConverterEx();
    }

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

    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
}

6.测试

    @ResponseBody
    @LoginRequired
    @RequestMapping(value = "/modifyUserInfo")
    public String modifyUserInfo(@CurrentUser SysUser user, String userId, String userName, String realName, String cellphone, String emodelId, String email, String description) {
        logger.info(user.getUserName());
        logger.info("registeredUser:userId:" + userId + ",userName:" + userName + ",realName:" + realName + ",cellphone:" + cellphone + ",emodelId:" + emodelId + ",email" + email + ",description:" + description);
        JSONObject jsonObject = sysUserServices.modifyUserInfo(userId, userName, realName, cellphone, emodelId, email, description);
        return jsonObject.toJSONString();
    }   


package com.xxx.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * 按照官方的建议放在root目录下,这样才能扫描到Service和dao,不然还会引起,扫描不到注解的问题
 * @author luomouren
 */
@SpringBootApplication
// mapper.java扫描
@MapperScan("com.xxx.demo.mapper")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

猜你喜欢

转载自blog.csdn.net/emily201314/article/details/78881192