redis jwt spring boot spring security 实现api token 验证

Posted haha174

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis jwt spring boot spring security 实现api token 验证相关的知识,希望对你有一定的参考价值。

文章地址:http://www.haha174.top/article/details/258083
项目源码:https://github.com/haha174/jwt-token.git
具体的实际效果可以看考这里 目前已经部署一个 个人测试机器上面:
http://cloud.codeguoj.cn/api-cloud-server/swagger-ui.html#!/token45controller/loginUsingPOST
相信很多人都调用过api, 一般的大致基本步骤都是先用登陆获得一个token,然后使用token调用api 或者直接给你一个token 凭借token调用api.
那么这里实现一个spring security+ redis+ jwt 发token 和验证token的一个项目。
首先需要安装redis 如果不会的小伙伴可以参考http://www.haha174.top/article/details/257478 这篇博客。
创建一个maven项目导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wen.security</groupId>
    <artifactId>jwt-token</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    </properties>
    <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-parent</artifactId>
           <version>1.5.6.RELEASE</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.6</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.6.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.44</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>

    </dependencies>
</project>

编写 token 拦截器 通过重写springsecurity AbstractAuthenticationProcessingFilter

public class AuthTokenFilter extends AbstractAuthenticationProcessingFilter
{
  Logger logger = LoggerFactory.getLogger(this.getClass());

  @Value("${token.header}")
  private String tokenHeader;
  
  @Value("${token.name}")
  private String tokenName;
  
  public AuthTokenFilter(RequestMatcher matcher)
  {
    super(matcher);
  }
  
  @Override
  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
      throws AuthenticationException, IOException, ServletException
  {
    String authToken = request.getHeader(this.tokenHeader);
    if(StringUtils.isEmpty(authToken))
    {
      authToken = request.getParameter(this.tokenName);
    }
    logger.debug("start to check token:{} *************");
    if (authToken == null)
    {
      throw new AuthenticationCredentialsNotFoundException("Access Token is not provided");
    }
    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(authToken, null);
    return getAuthenticationManager().authenticate(authentication);
    
  }
  
  @Override
  protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
      Authentication auth) throws IOException, ServletException
  {
    SecurityContext context = SecurityContextHolder.createEmptyContext();
    context.setAuthentication(auth);
    SecurityContextHolder.setContext(context);
    chain.doFilter(request, response);
  }

  @Override
  protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
      AuthenticationException authException) throws IOException, ServletException
  {
    SecurityContextHolder.clearContext();
    response.setStatus(HttpStatus.UNAUTHORIZED.value());
    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    String message;
    if (authException.getCause() != null)
    {
      message = authException.getCause().getMessage();
    }
    else
    {
      message = authException.getMessage();
    }

    BaseResponse baseResponse = new BaseResponse();
    baseResponse.setStatusCode(IConstants.RESPONSE_STATUS_CODE_FAILED);
    baseResponse.setStatusMsg(message);

    byte[] body = new ObjectMapper().writeValueAsBytes(baseResponse);
    response.getOutputStream().write(body);
  }
}

自定义校验

@Component
public class AuthTokenProvider implements AuthenticationProvider
{
  Logger logger = LoggerFactory.getLogger(this.getClass());

  @Autowired
  protected HttpServletRequest request;

  @Autowired
  TokenService tokenService;
  
  @Override
  public Authentication authenticate(Authentication authentication) throws AuthenticationException
  {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    if(auth!=null && auth.isAuthenticated())
    {
      return new UsernamePasswordAuthenticationToken(auth.getPrincipal(), null, new ArrayList<>());
    }
    String token = (String) authentication.getPrincipal();
    if (token != null)
    {
      if (!tokenService.checkToken(token))
      {
        throw new CredentialsExpiredException("Access Token is expired. Please login again.");
      }
    }
    else
    {
      throw new BadCredentialsException("Invalid token String.");
    }
    logger.debug("Authenticated successfully.");
    return new UsernamePasswordAuthenticationToken(token, null, new ArrayList<>());
  }
  
  @Override
  public boolean supports(Class<?> authentication)
  {
    return authentication.equals(UsernamePasswordAuthenticationToken.class);
  }

}

配置spring security 将上述的拦截器配置到spring security 的拦截器链上 和拦截规则

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebAuthConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;

    @Bean
    public AuthTokenFilter authenticationTokenFilterBean() throws Exception {
        List<String> pathsToSkip = Arrays.asList("/login");
        List<String> processingPath = Arrays.asList("/service/**","/logout");
        SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, processingPath);
        AuthTokenFilter filter = new AuthTokenFilter(matcher);
        filter.setAuthenticationManager(authenticationManager);
        return filter;
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("*"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                .antMatchers("/", "/*.html", "/**/favicon.ico", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
                .and().addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
        httpSecurity.cors().configurationSource(corsConfigurationSource());
        httpSecurity.logout().disable();
        httpSecurity.headers().cacheControl();

    }
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        filter.setForceEncoding(true);
        return filter;
    }
}

生成token 使用json web toekn

public class JavaWebToken {

    private static Logger log = LoggerFactory.getLogger(JavaWebToken.class);

    //该方法使用HS256算法和Secret:bankgl生成signKey
    private static Key getKeyInstance() {
        //We will sign our JavaWebToken with our ApiKey secret
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary("token");
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
        return signingKey;
    }

    //使用HS256签名算法和生成的signingKey最终的Token,claims中是有效载荷
    public static String createJavaWebToken(Map<String, Object> claims) {
        return   Jwts.builder()
                .setIssuer("Jersey-Security-Basic")//设置发行人
                .setSubject("subject")//设置抽象主题
                .setAudience("login")//设置角色
                .setExpiration(getDate())//过期时间
                .setIssuedAt(new Date())//设置现在时间
                .setClaims(claims)
                .signWith( SignatureAlgorithm.HS256,getKeyInstance())
                .compact();
    }

    //解析Token,同时也能验证Token,当验证失败返回null
    public static Map<String, Object> parserJavaWebToken(String jwt) {
        try {
            Map<String, Object> jwtClaims =
                    Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(jwt).getBody();
            return jwtClaims;
        } catch (Exception e) {
            log.error("json web token verify failed");
            return null;
        }
    }
    public static Date getDate(){
        try {
        /*    SimpleDateFormat format =  new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date date=format.parse(new Date().getTime()+new TokenProperties().getExpires()*1000+"");
            return date;*/
            long currentTime = System.currentTimeMillis() ;
            currentTime +=30*60*1000*2;
            Date date=new Date(currentTime);
            return date;
        }catch (Exception e){
            e.printStackTrace();
            long currentTime = System.currentTimeMillis() ;
            currentTime +=30*60*1000*2;
            Date date=new Date(currentTime);
            return date;
        }
    }
}

配置swagger
在请求头中加入header

 @Bean
    public Docket demoApi() {
        ParameterBuilder aParameterBuilder = new ParameterBuilder();
        aParameterBuilder.name("Authorization").description("input the token for authentication either in the authorization field or in the token field").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
        ParameterBuilder aParameterBuilder1 = new ParameterBuilder();
        aParameterBuilder1.name("token").description("input the token for authentication either in the authorization field or in the token field").modelRef(new ModelRef("string")).parameterType("query").required(false).build();
        List<Parameter> aParameters = new ArrayList<Parameter>();
        aParameters.add(aParameterBuilder.build());
        aParameters.add(aParameterBuilder1.build());
        return new Docket(DocumentationType.SWAGGER_2).apiInfo(getApiInfo()).useDefaultResponseMessages(false).globalOperationParameters(aParameters)
                .select()  .apis(RequestHandlerSelectors.basePackage("com.wen.token.web")).build();
    }
    protected ApiInfo getApiInfo()
    {
      return new ApiInfo("Rest Web Service", "total api  Rest Web Service " + new Date(), "", "",
          new Contact("cxhc", "", ""), "", "",new ArrayList<VendorExtension>());
    }

整合redis 可以参考这篇博客
http://www.haha174.top/article/details/251216
只需要启动应用 访问http://localhost:8080/swagger-ui.html#/
技术分享图片
----
技术分享图片














以上是关于redis jwt spring boot spring security 实现api token 验证的主要内容,如果未能解决你的问题,请参考以下文章

spring boot 使用 redis session

Redis 集群与 Spring Boot 的集成

spring boot + redis 实现session共享分析

Spring boot->redis value值出现两个双引号

一个基于Spring Boot+Vue+Redis的物联网智能家居系统,可二次开发接私活!

SpringSecurity注解鉴权(整合springboot,jwt,redis)