一个基于springSecurity的Json Web Token的实现
SecurityJwt
一个基于springSecurity的Json Web Token的实现
提要
一、SpringSecurity
Spring Security
,一种基于Spring AOP
和Servlet
过滤器的安全框架。它提供全面的安全性解决方案,同时在Web
请求级和方法调用级处理身份确认和授权。。- 来自
Spring
全家桶系列,与SpringBoot
无缝衔接。为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
二、JSON Web Token
-
JSON Web Token(JWT)
准确来说是一个规范。实际上就是一个字符串,它由三部分组成——头部(Header
)、载荷(playload
)与签名(signature
)。 -
头部(
Header
)用于描述关于该JWT
的最基本的信息,即该JWT
本身的信息声明,如签名所用算法。 -
载荷(
playload
)是存放有效信息的地方。其中信息又分为三个部分——声明部分、公共部分(subject
)、私有部分(claim
)。 -
签证(
signature
)需要base64
加密后的header
和base64
加密后的payload
使用.连接组成的字符串,然后通过header
中声明的加密方式进行加盐secret
组合加密构成(注意:secret
是保存在服务器端的)。 -
在分布式中直接根据
token
取出保存的用户信息,以及对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
为设置私有部分,其参数为key
—value
形式。setIssuedAt
为token
的签发时间。setNotBefore
为token
的生效时间。setExpiration
为token
的失效时间。
解析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 -> "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
如何使用——
-
登录之后,在响应
Response
的头Headers
里有字段Authorization
,该值就是Token
。 -
后续的访问需在
Request
的头Headers
内携带字段Authorization
和其值,以此来表示身份。 -
rememberMe
默认时效为一小时,为true
时时效7天,设置路径在org.zuoyu.security.jwt.constants.JwtConstants.java
。 -
测试路径查看
org.zuoyu.security.jwt.controller.AuthController.java
类。
该项目无任何依赖和侵入,拿来即用。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
RISC-V 指令集架构规范的 F# 实现开源
7 月份,RISC-V 基金会宣布批准 RISC-V基础指令集架构与特权架构规范,为 RISC-V 的可扩展性进一步奠定了基础。而近日,有开发者在此基础上开源了一份以F# 实现的RISC-V 正式 ISA(指令集架构)规范(实现)。 RISC-V基础指令集架构是应用软件与硬件之间的接口,编码为此规范的软件将继续在 RISC-V 处理器上永久使用,即使架构通过开发新扩展而发展也不受影响。 官方表示 RISC-V 采用简单的固定基础 ISA 和模块化固定标准扩展设计,有助于防止碎片化,同时支持定制,RISC-V 生态已经在此前的各种实现上展示了很大程度的互操作性,而现在批准了基础架构规范,开发人员可以进一步放心,他们为 RISC-V 编写的软件将可以一直运行在所有类似的 RISC-V 核心上。 特权架构则用于在软件堆栈的不同组件之间提供保护,并且尝试执行当前权限模式不允许的操作将导致引发异常。RISC-V 特权架构涵盖了非特权 ISA 之外的 RISC-V 系统的所有方面,包括特权指令以及运行操作系统和连接外部设备所需的其它功能。每个权限级别都有一组核心的特权 ISA 扩展,带有可选的扩展...
- 下一篇
马蜂窝 IM 移动端架构的从 0 到 1
(马蜂窝技术原创内容,公众号 ID:mfwtech) 移动互联网技术改变了旅游的世界,这个领域过去沉重的信息分销成本被大大降低。用户与服务供应商之间、用户与用户之间的沟通路径逐渐打通,沟通的场景也在不断扩展。这促使所有的移动应用开发者都要从用户视角出发,更好地满足用户需求。 论坛时代的马蜂窝,用户之间的沟通形式比较单一,主要为单纯的回帖回复等。为了以较小的成本快速满足用户需求,当时采用的是非实时性消息的方案来实现用户之间的消息传递。 随着行业和公司的发展,马蜂窝确立了「内容+交易」的独特商业模式。在用户规模不断增长及业务形态发生变化的背景下,为用户和商家提供稳定可靠的售前和售后技术支持,成为电商移动业务线的当务之急。 一、设计思路与整体架构 我们结合 B2C,C2B,C2C 不同的业务场景设计实现了马蜂窝旅游移动端中的私信、用户咨询、用户反馈等即时通讯业务;同时为了更好地为合作商家赋能,在马蜂窝商家移动端中加入与会话相关的咨询用户管理、客服管理、运营资源统计等功能。 目前 IM 涉及到的业务如下: 为了实现马蜂窝旅游 App 及商家 IM 业务逻辑、公共资源的整合复用及 UI 个性化...
相关文章
文章评论
共有0条评论来说两句吧...