详解SpringCloud服务认证(JWT)

 更新时间:2018年01月27日 16:58:34   作者:唐亚峰  
本篇文章主要介绍了SpringCloud服务认证(JWT),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

 - JWT

JWT(JSON Web Token), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

- JWT与其它的区别

通常情况下,把API直接暴露出去是风险很大的,不说别的,直接被机器攻击就喝一壶的。那么一般来说,对API要划分出一定的权限级别,然后做一个用户的鉴权,依据鉴权结果给予用户开放对应的API。目前,比较主流的方案有几种:

OAuth

OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在某一服务上存储的私密的资源(如照片,视频),而无需将用户名和密码提供给第三方应用。

OAuth 允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容

Cookie/Session Auth

Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个Cookie对象;通过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理的。默认的,当我们关闭浏览器的时候,cookie会被删除。但可以通过修改cookie 的expire time使cookie在一定时间内有效,基于session方式认证势必会对服务器造成一定的压力(内存存储),不易于扩展(需要处理分布式session),跨站请求伪造的攻击(CSRF)

- JWT的优点

1.相比于session,它无需保存在服务器,不占用服务器内存开销。

2.无状态、可拓展性强:比如有3台机器(A、B、C)组成服务器集群,若session存在机器A上,session只能保存在其中一台服务器,此时你便不能访问机器B、C,因为B、C上没有存放该Session,而使用token就能够验证用户请求合法性,并且我再加几台机器也没事,所以可拓展性好就是这个意思。

3.前后端分离,支持跨域访问。

- JWT的组成

{ "iss": "JWT Builder", 
 "iat": 1416797419, 
 "exp": 1448333419, 
 "aud": "www.battcn.com", 
 "sub": "1837307557@qq.com", 
 "GivenName": "Levin", 
 "Surname": "Levin", 
 "Email": "1837307557@qq.com", 
 "Role": [ "ADMIN", "MEMBER" ] 
}
  1.  iss: 该JWT的签发者,是否使用是可选的;
  2. sub: 该JWT所面向的用户,是否使用是可选的;
  3. aud: 接收该JWT的一方,是否使用是可选的;
  4. exp(expires): 什么时候过期,这里是一个Unix时间戳,是否使用是可选的;
  5. iat(issued at): 在什么时候签发的(UNIX时间),是否使用是可选的;
  6. nbf (Not Before):如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟;,是否使用是可选的;

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷、签名(上图依次排序)

JWT Token生成器:https://jwt.io/

- 认证

- 登陆认证

  1. 客户端发送 POST 请求到服务器,提交登录处理的Controller层
  2. 调用认证服务进行用户名密码认证,如果认证通过,返回完整的用户信息及对应权限信息
  3. 利用 JJWT 对用户、权限信息、秘钥构建Token
  4. 返回构建好的Token

- 请求认证

  1. 客户端向服务器请求,服务端读取请求头信息(request.header)获取Token
  2. 如果找到Token信息,则根据配置文件中的签名加密秘钥,调用JJWT Lib对Token信息进行解密和解码;
  3. 完成解码并验证签名通过后,对Token中的exp、nbf、aud等信息进行验证;
  4. 全部通过后,根据获取的用户的角色权限信息,进行对请求的资源的权限逻辑判断;
  5. 如果权限逻辑判断通过则通过Response对象返回;否则则返回HTTP 401;

无效Token

有效Token

- JWT的缺点

有优点就会有缺点,是否适用应该考虑清楚,而不是技术跟风

  1. token过大容易占用更多的空间
  2. token中不应该存储敏感信息
  3. JWT不是 session ,勿将token当session
  4. 无法作废已颁布的令牌,因为所有的认证信息都在JWT中,由于在服务端没有状态,即使你知道了某个JWT被盗取了,你也没有办法将其作废。在JWT过期之前(你绝对应该设置过期时间),你无能为力。
  5. 类似缓存,由于无法作废已颁布的令牌,在其过期前,你只能忍受”过期”的数据(自己放出去的token,含着泪也要用到底)。

- 代码(片段)

TokenProperties 与 application.yml资源的key映射,方便使用

@Configuration
@ConfigurationProperties(prefix = "battcn.security.token")
public class TokenProperties {
 /**
 * {@link com.battcn.security.model.token.Token} token的过期时间
 */
 private Integer expirationTime;

 /**
 * 发行人
 */
 private String issuer;

 /**
 * 使用的签名KEY {@link com.battcn.security.model.token.Token}.
 */
 private String signingKey;

 /**
 * {@link com.battcn.security.model.token.Token} 刷新过期时间
 */
 private Integer refreshExpTime;

 // get set ...
}

Token生成的类

@Component
public class TokenFactory {

 private final TokenProperties properties;

 @Autowired
 public TokenFactory(TokenProperties properties) {
 this.properties = properties;
 }

 /**
 * 利用JJWT 生成 Token
 * @param context
 * @return
 */
 public AccessToken createAccessToken(UserContext context) {
 Optional.ofNullable(context.getUsername()).orElseThrow(()-> new IllegalArgumentException("Cannot create Token without username"));
 Optional.ofNullable(context.getAuthorities()).orElseThrow(()-> new IllegalArgumentException("User doesn't have any privileges"));
 Claims claims = Jwts.claims().setSubject(context.getUsername());
 claims.put("scopes", context.getAuthorities().stream().map(Object::toString).collect(toList()));
 LocalDateTime currentTime = LocalDateTime.now();
 String token = Jwts.builder()
  .setClaims(claims)
  .setIssuer(properties.getIssuer())
  .setIssuedAt(Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant()))
  .setExpiration(Date.from(currentTime
  .plusMinutes(properties.getExpirationTime())
  .atZone(ZoneId.systemDefault()).toInstant()))
  .signWith(SignatureAlgorithm.HS512, properties.getSigningKey())
 .compact();
 return new AccessToken(token, claims);
 }

 /**
 * 生成 刷新 RefreshToken
 * @param userContext
 * @return
 */
 public Token createRefreshToken(UserContext userContext) {
 if (StringUtils.isBlank(userContext.getUsername())) {
  throw new IllegalArgumentException("Cannot create Token without username");
 }
 LocalDateTime currentTime = LocalDateTime.now();
 Claims claims = Jwts.claims().setSubject(userContext.getUsername());
 claims.put("scopes", Arrays.asList(Scopes.REFRESH_TOKEN.authority()));
 String token = Jwts.builder()
  .setClaims(claims)
  .setIssuer(properties.getIssuer())
  .setId(UUID.randomUUID().toString())
  .setIssuedAt(Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant()))
  .setExpiration(Date.from(currentTime
  .plusMinutes(properties.getRefreshExpTime())
  .atZone(ZoneId.systemDefault()).toInstant()))
  .signWith(SignatureAlgorithm.HS512, properties.getSigningKey())
 .compact();

 return new AccessToken(token, claims);
 }
}

配置文件,含token过期时间,秘钥,可自行扩展

battcn:
 security:
 token:
 expiration-time: 10 # 分钟 1440
 refresh-exp-time: 30 # 分钟 2880
 issuer: http://blog.battcn.com
 signing-key: battcn

WebSecurityConfig 是 Spring Security 关键配置,在Securrty中基本上可以通过定义过滤器去实现我们想要的功能.

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

 public static final String TOKEN_HEADER_PARAM = "X-Authorization";
 public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login";
 public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
 public static final String MANAGE_TOKEN_BASED_AUTH_ENTRY_POINT = "/manage/**";
 public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token";

 @Autowired private RestAuthenticationEntryPoint authenticationEntryPoint;
 @Autowired private AuthenticationSuccessHandler successHandler;
 @Autowired private AuthenticationFailureHandler failureHandler;
 @Autowired private LoginAuthenticationProvider loginAuthenticationProvider;
 @Autowired private TokenAuthenticationProvider tokenAuthenticationProvider;

 @Autowired private TokenExtractor tokenExtractor;

 @Autowired private AuthenticationManager authenticationManager;

 protected LoginProcessingFilter buildLoginProcessingFilter() throws Exception {
 LoginProcessingFilter filter = new LoginProcessingFilter(FORM_BASED_LOGIN_ENTRY_POINT, successHandler, failureHandler);
 filter.setAuthenticationManager(this.authenticationManager);
 return filter;
 }

 protected TokenAuthenticationProcessingFilter buildTokenAuthenticationProcessingFilter() throws Exception {
 List<String> list = Lists.newArrayList(TOKEN_BASED_AUTH_ENTRY_POINT,MANAGE_TOKEN_BASED_AUTH_ENTRY_POINT);
 SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(list);
 TokenAuthenticationProcessingFilter filter = new TokenAuthenticationProcessingFilter(failureHandler, tokenExtractor, matcher);
 filter.setAuthenticationManager(this.authenticationManager);
 return filter;
 }

 @Bean
 @Override
 public AuthenticationManager authenticationManagerBean() throws Exception {
 return super.authenticationManagerBean();
 }

 @Override
 protected void configure(AuthenticationManagerBuilder auth) {
 auth.authenticationProvider(loginAuthenticationProvider);
 auth.authenticationProvider(tokenAuthenticationProvider);
 }

 @Override
 protected void configure(HttpSecurity http) throws Exception {
 http
 .csrf().disable() // 因为使用的是JWT,因此这里可以关闭csrf了
 .exceptionHandling()
 .authenticationEntryPoint(this.authenticationEntryPoint)
 .and()
  .sessionManagement()
  .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
 .and()
  .authorizeRequests()
  .antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point
  .antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point
 .and()
  .authorizeRequests()
  .antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points
  .antMatchers(MANAGE_TOKEN_BASED_AUTH_ENTRY_POINT).hasAnyRole(RoleEnum.ADMIN.name())
 .and()
  .addFilterBefore(buildLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
  .addFilterBefore(buildTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
 }
}

- 说点什么

由于JWT代码做了简单封装,包含内容较多,所以文章里只贴主要片段,需要完整代码可以直接从下面GIT获取

本章代码(battcn-jwt-service):http://xiazai.jb51.net/201801/yuanma/battcn-cloud_jb51.rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Spring中@order注解用法实战教程

    Spring中@order注解用法实战教程

    @Order注解主要用来控制配置类的加载顺序,数字越小,越先加载,下面这篇文章主要给大家介绍了关于Spring中@order注解用法的相关资料,需要的朋友可以参考下
    2022-11-11
  • SpringBoot项目中使用缓存Cache的正确方法分享

    SpringBoot项目中使用缓存Cache的正确方法分享

    缓存可以通过将经常访问的数据存储在内存中,减少底层数据源如数据库的压力,从而有效提高系统的性能和稳定性。本文就来讲讲SpringBoot项目中使用缓存Cache的正确姿势吧
    2023-04-04
  • Java程序中添加播放MIDI音乐功能的实现方法详解

    Java程序中添加播放MIDI音乐功能的实现方法详解

    本篇文章是对在Java程序中添加播放MIDI音乐功能的方法进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • java读取ftp中TXT文件的案例

    java读取ftp中TXT文件的案例

    这篇文章主要介绍了java读取ftp中TXT文件的案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Java类的初始化顺序知识点总结

    Java类的初始化顺序知识点总结

    在本篇文章里小编给大家整理的是关于Java类的初始化顺序知识点总结,需要的朋友们可以学习下。
    2020-02-02
  • Java fastdfs客户端实现上传下载文件

    Java fastdfs客户端实现上传下载文件

    这篇文章主要介绍了Java fastdfs客户端实现上传下载文件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • SpringCloud开发课程查询功能

    SpringCloud开发课程查询功能

    这篇文章主要介绍了SpringCloud开发课程查询功能,本文通过图文实例相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • spring boot切面execution表达式添加多个包路径问题及解决方案

    spring boot切面execution表达式添加多个包路径问题及解决方案

    在Spring Boot中,如果你想为多个包中的方法创建一个切面,你可以在@Pointcut注解中使用||操作符来指定多个包,下面给大家分享spring boot切面execution表达式添加多个包路径问题及解决方案,感兴趣的朋友跟随小编一起看看吧
    2024-03-03
  • Java中难理解的四个概念

    Java中难理解的四个概念

    这篇文章主要介绍了匿名内部类、多线程、如何实现同步、序列化,这四个难理解的概念,同学们一定要仔细看看
    2021-04-04
  • Java异步非阻塞编程的几种方式总结

    Java异步非阻塞编程的几种方式总结

    这篇文章主要介绍了Java异步非阻塞编程的几种方式总结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06

最新评论