文档章节

一个基于springSecurity的Json Web Token的实现

左羽
 左羽
发布于 10/20 22:41
字数 1442
阅读 741
收藏 16

SecurityJwt

一个基于springSecurity的Json Web Token的实现

GitHub地址


提要

一、SpringSecurity

  • Spring Security,一种基于 Spring AOPServlet 过滤器的安全框架。它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权。。
  • 来自Spring全家桶系列,与SpringBoot无缝衔接。为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

二、JSON Web Token

  • JSON Web Token(JWT)准确来说是一个规范。实际上就是一个字符串,它由三部分组成——头部Header、载荷playload与签名signature)。

  • 头部Header)用于描述关于该JWT的最基本的信息,即该JWT本身的信息声明,如签名所用算法。

  • 载荷playload)是存放有效信息的地方。其中信息又分为三个部分——声明部分公共部分(subject)私有部分(claim)

  • 签证signature)需要base64加密后的headerbase64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密构成(注意secret是保存在服务器端的)。

  • 在分布式中直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。

  • Image of Token

三、开发环境介绍

  • Java版本:1.8
  • 构建工具:Gradle(目前国内主流构建工具依然是Maven,但是笔者用过Gradle之后就不想再用Maven了,因为Gradle是真的方便很多。其仓库结构向下兼容Maven,也就是说可以使用任何Maven仓库。

build.gradle文件:

plugins {
    id 'org.springframework.boot' version '2.2.0.RELEASE'
    id 'io.spring.dependency-management' version '1.0.8.RELEASE'
    id 'java'
}

group = 'org.zuoyu'
version = '1.0.0'
sourceCompatibility = '1.8'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
//    这里使用的是阿里巴巴的Maven仓库
    maven {
        url 'http://maven.aliyun.com/nexus/content/groups/public/'
    }
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'mysql:mysql-connector-java'
    annotationProcessor 'org.projectlombok:lombok'
//    jwt依赖
    runtime('io.jsonwebtoken:jjwt-jackson:0.10.7')
    runtime('io.jsonwebtoken:jjwt-impl:0.10.7')
    compile('io.jsonwebtoken:jjwt-api:0.10.7')
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'org.springframework.security:spring-security-test'
}

test {
    useJUnitPlatform()
}

四、源码说明

在这里只分析关键代码(其中的JwtConstants.java是我自定义的final变量类)

备注:security的配置文件中,将session管理器关闭,没有必要使用session

1. JwtTokenUtils.java(JWT的工具类)

/**
   * 构建JWT
   *
   * @param subject - 实体
   * @param authorities - 权限
   * @param expiration - 保留时间
   * @return - token
   */
  private static String createJwt(String subject,
      String authorities, long expiration) {
    long nowMillis = System.currentTimeMillis();
    return Jwts.builder()
        .setId(JwtConstants.createTokenId())
        .signWith(SECRET_KEY, SignatureAlgorithm.HS256)
        .setIssuer(JwtConstants.JWT_ISSUER)
        .setSubject(subject)
        .claim(JwtConstants.ROLE_CLAIMS, authorities)
        .setIssuedAt(new Date(nowMillis))
        .setNotBefore(new Date(nowMillis))
        .setExpiration(new Date(nowMillis + expiration * 1000L))
        .compact();
  }

在这里我们使用官方依赖包中的Jwts.builder()方法,创建一个token,其中——

  • signWith就是设置私密钥与加密方式,SECRET_KEY为私密钥,SignatureAlgorithm.HS256为加密方式。
  • setSubject为设置公共部分,该部分在客户端可解密。
  • claim为设置私有部分,其参数为keyvalue形式。
  • setIssuedAttoken的签发时间。
  • setNotBeforetoken的生效时间。
  • setExpirationtoken的失效时间。

解析token

/**
   * 解析token
   *
   * @param token -
   * @return - Claims
   */
  private static Claims parseJwt(String token) {
    return Jwts.parser()
        .setSigningKey(SECRET_KEY)
        .parseClaimsJws(token)
        .getBody();
  }

在这里重点在与setSigningKey,传入我们在创建时候的私密钥SECRET_KEY

还有几个与security方便交互的方法:

 /**
   * 根据账户构建token
   *
   * @param user - 账户
   * @return -
   */
  public static String createToken(User user, boolean isRememberMe) {
    long expiration =
        isRememberMe ? JwtConstants.EXPIRATION_REMEMBER : JwtConstants.EXPIRATION;
    String spacer = ",";
    List<string> authorities = Arrays.stream(user.getRoles().split(spacer))
        .map(role -&gt; "ROLE_" + role)
        .collect(Collectors.toList());
    return createJwt(JsonUtil.beanToJsonString(user), JsonUtil.objectToJsonString(authorities),
        expiration);
  }



  /**
   * 获取用户
   *
   * @param token - token
   * @return - User
   */
  public static User getUserByToken(String token) {
    String subject = parseJwt(token).getSubject();
    return JsonUtil.jsonStringToBean(subject, User.class);
  }


/**
   * 获取用户的权限
   * @param token - token
   * @return - 权限列表
   */
  public static Collection<!--? extends GrantedAuthority--> getAuthoritiesByToken(String token) {
    String roles = parseJwt(token).get(JwtConstants.ROLE_CLAIMS).toString();
    return JsonUtil.jsonStringToCollection(roles, SimpleGrantedAuthority.class);
  }

2. AuthenticationSuccessHandlerImpl.java(Security登录成功后的执行行为)

/**
 * 登录成功的实现.
 *
 * @author zuoyu
 **/
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {

  @Override
  public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
      Authentication authentication) throws IOException {
    String rememberMe = request.getParameter(JwtConstants.USER_LOGIN_REMEMBER_ME);
    boolean isRememberMe = Boolean.parseBoolean(rememberMe);
    User principal = (User) authentication.getPrincipal();
    String token = JwtTokenUtils.createToken(principal, isRememberMe);
    response.setContentType("application/json;charset=utf-8");
    response.setHeader(JwtConstants.TOKEN_HEADER, token);
    response.setStatus(HttpServletResponse.SC_OK);
    PrintWriter responseWriter = response.getWriter();
    responseWriter.write("{\"message\":\"登录成功\"}");
    responseWriter.flush();
    responseWriter.close();
  }


}

这段代码主要思路是——登录成功后,在authentication中获取已经认证成功的用户信息(user),然后将该user转换为token并返回给客户端。其中的isRememberMe是根据是否为true给予token不同的有效时间(查看完整源代码)。

3. JwtAuthorizationFilter.java(自定义基于JWT认证的过滤器)

/**
 * JWT的权限过滤器.
 *
 * @author zuoyu
 * @program jwt
 * @create 2019-10-17 16:26
 **/
@Slf4j
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {

  public JwtAuthorizationFilter(AuthenticationManager authenticationManager) {
    super(authenticationManager);
  }

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
      FilterChain chain) throws IOException, ServletException {
    String token = request.getHeader(JwtConstants.TOKEN_HEADER);
    if (StringUtils.isEmpty(token)) {
      chain.doFilter(request, response);
      return;
    }
    User user = JwtTokenUtils.getUserByToken(token);
    Collection<!--? extends GrantedAuthority--> authorities = JwtTokenUtils.getAuthoritiesByToken(token);
    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
        user, null, authorities);
    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
    super.doFilterInternal(request, response, chain);
  }
}

这段代码的从请求中获取token,并将从token中解析出用户信息(user)和权限信息(authorities)。并根据用户信息(user)和权限信息(authorities)创建属于security框架的权限身份(authentication),将其存入当前的security环境。

五、使用方法

  • 项目启动后自动建表(JPA)

  • 注册(登录)的账户名称字段为userName

  • 注册(登录)的账户密码字段为passWord

  • 登录时的“记住我”字段为rememberMe

如何使用——

  1. 登录之后,在响应Response的头Headers里有字段Authorization,该值就是Token

  2. 后续的访问需在Request的头Headers内携带字段Authorization和其值,以此来表示身份。

  3. rememberMe默认时效为一小时,为true时时效7天,设置路径在org.zuoyu.security.jwt.constants.JwtConstants.java

  4. 测试路径查看org.zuoyu.security.jwt.controller.AuthController.java类。


该项目无任何依赖和侵入,拿来即用。

© 著作权归作者所有

上一篇: Java8之stream流
下一篇: Java8之Lambda语法
左羽
粉丝 0
博文 6
码字总数 17865
作品 0
郑州
后端工程师
私信 提问
SpringBoot+mybatis+springsecurity实现用户角色数据库管理

SpringSecurity是专门针对基于Spring项目的安全框架,充分利用了依赖注入和AOP来实现安全管控。在很多大型企业级系统中权限是最核心的部分,一个系统的好与坏全都在于权限管控是否灵活,是否...

ben4
2017/11/24
0
0
使用Spring Security开发基于表单的认证(二)

使用Spring Security开发基于表单的认证(二) 个性化用户认证流程 一、自定义登录页面 ①加页面:定义该页面hcx-signIn.html为登录页面: ②配授权 hcx-signIn.html: 注意,如果忘记配授权...

JS_HCX
2018/04/30
0
0
编程界的小学生/common-security

common-security 目前包含如下几大模块: common-security-corecommon-security-validatecommon-security-browsercommon-security-app 一、common-security-validate 1、是什么? 通用的验证......

编程界的小学生
2018/07/03
0
0
Springsecurity-oauth2之/oauth/token的处理

Springsecurity-oauth2的版本是2.2.1.RELEASE. 使用postman进行/oauth/token的时候,服务端Springsecurity是怎么处理的呢? 图1 图2 图3 上面的图2和图3,我们就会从服务端获得token。 来看B...

克虏伯
04/01
173
0
使用Spring Security开发基于表单的认证(一)

使用Spring Security开发基于表单的认证(一) SpringSecurity核心功能: 认证(你是谁) 授权(你能干什么) 攻击防护(防止伪造身份) 使用springsecurity的默认安全机制: 访问接口时,会弹...

JS_HCX
2018/04/29
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Vue.js学习笔记2 - better-scroll滚动条

better-scroll滚动条 使用作者自制的better-scroll库,实现内容的滚动。 先在package.json加上依赖: "better-scroll": "^0.1.7" 接着再npm install安装依赖。 import BScroll from 'better-......

swanf
今天
7
0
设计模式之适配器模式

定义 将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工 作。 UML类图 适配器分为两种,类适配器与对象适配器。 类适配器的UML图...

陈年之后是青葱
今天
8
0
教你玩转Linux—磁盘管理

导读 Linux磁盘管理好坏直接关系到整个系统的性能问题,Linux磁盘管理常用三个命令为df、du和fdisk。 df df命令参数功能:检查文件系统的磁盘空间占用情况。可以利用该命令来获取硬盘被占用了...

问题终结者
今天
13
0
KMP

字符串匹配算法 针对被匹配字段生产一个部分匹配表 A B C D A B D 0 0 0 0 1 2 0 部分匹配表 熟悉前缀与后缀的概念 ,“部分匹配表” 的生产就是根据前缀、后缀的最苍的共有元素的长度 前缀:...

鬼才王
昨天
6
0
快速搭建Jenkins集群

关于Jenkins集群 在Jenkins上同时执行多个任务时,单机性能可能达到瓶颈,使用Jenkins集群可以有效的解决此问题,让多台机器同时处理这些任务可以将压力分散,对单机版Jenkins的单点故障的隐...

程序员欣宸
昨天
13
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部