项目集成Spring Security(一)

在上一篇基础上继续集成 JWT ,实现用户身份验证。

前言

前后端分离项目中,如果直接把 API 接口对外开放,我们知道这样风险是很大的,所以在上一篇中我们引入了 Spring Security ,但是我们在登陆后缺少了请求凭证部分。

什么是JWT?

JWT是 Json Web Token 的缩写。它是基于 RFC 7519 标准定义的一种可以安全传输的 小巧 和 自包含 的JSON对象。由于数据是使用数字签名的,所以是可信任的和安全的。JWT可以使用HMAC算法对secret进行加密或者使用RSA的公钥私钥对来进行签名。

JWT的工作流程

1、用户进入登录页,输入用户名、密码,进行登录;
2、服务器验证登录鉴权,如果改用户合法,根据用户的信息和服务器的规则生成 JWT Token
3、服务器将该 token 以 json 形式返回(不一定要json形式,这里说的是一种常见的做法)
4、用户得到 token,存在 localStorage、cookie 或其它数据存储形式中。以后用户请求 /protected 中的 API 时,在请求的 header 中加入 Authorization: Bearer xxxx(token)。此处注意token之前有一个7字符长度的 Bearer。
5、服务器端对此 token 进行检验,如果合法就解析其中内容,根据其拥有的权限和自己的业务逻辑给出对应的响应结果。
6、用户取得结果

如下如所示:

7790cc3aade467c985e2e4a8105b89f1.png

来看一下 JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

token 分成了三部分,头部(header),荷载(Payload) 和 签名(Signature),每部分用 . 分隔,其中头部和荷载使用了base64编码,分别解码之后得到两个JSON串:

第一部分-头部:

{
  "alg": "HS256",
  "typ": "JWT"
}

alg字段为加密算法,这是告诉我们 HMAC 采用 HS512 算法对 JWT 进行的签名。

第二部分-荷载:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

荷载的字段及含义:

  • iss: 该JWT的签发者
  • sub: 该JWT所面向的用户
  • aud: 接收该JWT的一方
  • exp(expires): 什么时候过期,这里是一个Unix时间戳
  • iat(issued at): 在什么时候签发的

这段告诉我们这个Token中含有的数据声明(Claim),这个例子里面有三个声明:sub, name 和 iat。在我们这个例子中,分别代表着
所面向的用户、用户名、创建时间,当然你可以把任意数据声明在这里。

第三部分-签名:

第三部分签名则不能使用base64解码出来,该部分用于验证头部和荷载数据的完整性。

JWT的生成和解析

引入依赖:

<!-- JWT -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

创建一个测试类尝试一下 JWT 的生成:

public class Test {

    public static void main(String[] args){
        String token = Jwts.builder()
                主题 放入用户名
                .setSubject("niceyoo")
                自定义属性 放入用户拥有请求权限
                .claim("authorities","admin")
                失效时间
                .setExpiration(new Date(System.currentTimeMillis() + 7 * 60 * 1000))
                签名算法和密钥
                .signWith(SignatureAlgorithm.HS512, "tmax")
                .compact();
        System.out.println(token);
    } }

控制台打印如下:

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJuaWNleW9vIiwiYXV0aG9yaXRpZXMiOiJhZG1pbiIsImV4cCI6MTU1OTQ1ODM1M30.keCiHrcEr0IWXfZLocgHS8znn7uSiaZW1IT6bTs-EQG0NPsb6-Aw_XbGQea4mez2CcAflgMqtzIpsDjZsUOVug

数据声明(Claim)是一个自定义属性,可以用来放入用户拥有请求权限。上边为简单直接传了一个 'admin'。

再看看解析:

public static void main(String[] args){

    try {
        解析token
        Claims claims = Jwts.parser()
                .setSigningKey("tmax")
                .parseClaimsJws("eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJuaWNleW9vIiwiYXV0aG9yaXRpZXMiOiJhZG1pbiIsImV4cCI6MTU1OTQ1OTc2Mn0.MkSJtGaVePLa-eM3gylh1T3fwODg-6ceDDOxscXAQKun-qNrbQFcKPNqXhblbXPNLhaJyEnwugNANCTs98UNmA")
                .getBody();         System.out.println(claims);
        获取用户名
        String username = claims.getSubject();
        System.out.println("username:"+username);
        获取权限
        String authority = claims.get("authorities").toString();
        System.out.println("权限:"+authority);
    } catch (ExpiredJwtException e) {
        System.out.println("jwt异常");
    } catch (Exception e){
        System.out.println("异常");
    }
}

控制台打印:

{sub=niceyoo, authorities=admin, exp=1559459762}
username:niceyoo
权限:admin

JWT 本身没啥难度,但安全整体是一个比较复杂的事情,JWT 只不过提供了一种基于 token 的请求验证机制。但我们的用户权限,对于 API 的权限划分、资源的权限划分,用户的验证等等都不是JWT负责的。也就是说,请求验证后,你是否有权限看对应的内容是由你的用户角色决定的。所接下来才是我们的重点,Spring Security 整合 JWT。

集成JWT

要想要 JW T在 Spring 中工作,我们应该新建一个 JWT filter,并把它配置在 WebSecurityConfig 中。

WebSecurityConfigurerAdapter.java

@Slf4j
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {     @Autowired
    private UserDetailsServiceImpl userDetailsService;     @Autowired
    private AuthenticationSuccessHandler successHandler;     @Autowired
    private AuthenticationFailHandler failHandler;     @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());加密
    }     @Override
    protected void configure(HttpSecurity http) throws Exception {         ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http
                .authorizeRequests();         registry.and()
            表单登录方式
            .formLogin()
            .permitAll()
            成功处理类
            .successHandler(successHandler)
            失败
            .failureHandler(failHandler)
            .and()
            .logout()
            .permitAll()
            .and()
            .authorizeRequests()
            任何请求
            .anyRequest()
            需要身份认证
            .authenticated()
            .and()
            关闭跨站请求防护
            .csrf().disable()
            前后端分离采用JWT 不需要session
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            添加JWT过滤器 除已配置的其它请求都需经过此过滤器
            .addFilter(new JWTAuthenticationFilter(authenticationManager(), 7));
    }
}

相较于上一篇主要多了如下一行配置:

.addFilter(new JWTAuthenticationFilter(authenticationManager(), 7));

JWTAuthenticationFilter.java

@Slf4j
public class JWTAuthenticationFilter extends BasicAuthenticationFilter   {     private Integer tokenExpireTime;     public JWTAuthenticationFilter(AuthenticationManager authenticationManager, Integer tokenExpireTime) {
        super(authenticationManager);
        this.tokenExpireTime = tokenExpireTime;
    }     public JWTAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
        super(authenticationManager, authenticationEntryPoint);
    }     @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {         String header = request.getHeader(SecurityConstant.HEADER);
        if(StrUtil.isBlank(header)){
            header = request.getParameter(SecurityConstant.HEADER);
        }
        Boolean notValid = StrUtil.isBlank(header) || (!header.startsWith(SecurityConstant.TOKEN_SPLIT));
        if (notValid) {
            chain.doFilter(request, response);
            return;
        }
        try {
            UsernamePasswordAuthenticationToken 继承 AbstractAuthenticationToken 实现 Authentication
            所以当在页面中输入用户名和密码之后首先会进入到 UsernamePasswordAuthenticationToken验证(Authentication),
            UsernamePasswordAuthenticationToken authentication = getAuthentication(header, response);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }catch (Exception e){
            e.toString();
        }         chain.doFilter(request, response);
    }     private UsernamePasswordAuthenticationToken getAuthentication(String header, HttpServletResponse response) {         用户名
        String username = null;
        权限
        List<GrantedAuthority> authorities = new ArrayList<>();         try {
            解析token
            Claims claims = Jwts.parser()
                    .setSigningKey(SecurityConstant.JWT_SIGN_KEY)
                    .parseClaimsJws(header.replace(SecurityConstant.TOKEN_SPLIT, ""))
                    .getBody();
            logger.info("claims:"+claims);
            获取用户名
            username = claims.getSubject();
            logger.info("username:"+username);
            获取权限
            String authority = claims.get(SecurityConstant.AUTHORITIES).toString();
            logger.info("authority:"+authority);
            if(!StringUtils.isEmpty(authority)){
                authorities.add(new SimpleGrantedAuthority(authority));
            }         } catch (ExpiredJwtException e) {
            ResponseUtil.out(response, ResponseUtil.resultMap(false,401,"登录已失效,请重新登录"));
        } catch (Exception e){
            log.error(e.toString());
            ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"解析token错误"));
        }         if(StrUtil.isNotBlank(username)) {
            踩坑提醒 此处password不能为null
            User principal = new User(username, "", authorities);
            return new UsernamePasswordAuthenticationToken(principal, null, authorities);
        }
        return null;
    }
}

接下来我们启动项目看看:

访问项目中已有的链接:

http://localhost:7777/tmax/videoCategory/getAll

老样子认证一波:

其中 niceyoo、 为数据库用户信息

登陆成功后获取返回的 token,注意,此 token 是由 JWT 生成的:

 String token = SecurityConstant.TOKEN_SPLIT + Jwts.builder()
            主题 放入用户名
            .setSubject(username)
            自定义属性 放入用户拥有请求权限
            .claim(SecurityConstant.AUTHORITIES, authorities)
            失效时间
            .setExpiration(new Date(System.currentTimeMillis() + 7 * 60 * 1000))
            签名算法和密钥
            .signWith(SignatureAlgorithm.HS512, SecurityConstant.JWT_SIGN_KEY)
            .compact();

浏览器返回 token 如下:

ad45accf0b31c606a10c568acbddec19.png

然后我们通过 token 凭证去访问上边的方法:

e3c05b207e1ec11e1f23560ef1b724b6.png

后台打印信息:

claims:{sub=niceyoo, authorities=admin, exp=1559472866}
username:niceyoo
authority:admin

随便改一下 token ,返回如下:

 
18年专科毕业后,期间一度迷茫,最近我创建了一个公众号用来记录自己的成长。

SpringSecurity 整合 JWT的更多相关文章

  1. SpringSecurity权限管理系统实战—六、SpringSecurity整合jwt

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  2. SpringSecurity整合JWT

    一.前言 最近负责支付宝小程序后端项目设计,这里主要分享一下用户会话.接口鉴权的设计.参考过微信小程序后端的设计,会话需要依靠redis.相关的开发人员和我说依靠Redis并不是很靠谱,redis在业 ...

  3. 【SpringBoot技术专题】「JWT技术专区」SpringSecurity整合JWT授权和认证实现

    JWT基本概念 JWT,即 JSON Web Tokens(RFC 7519),是一个广泛用于验证 REST APIs 的标准.虽说是一个新兴技术,但它却得以迅速流行. JWT的验证过程是: 前端(客 ...

  4. SpringBoot整合SpringSecurity实现JWT认证

    目录 前言 目录 1.创建SpringBoot工程 2.导入SpringSecurity与JWT的相关依赖 3.定义SpringSecurity需要的基础处理类 4. 构建JWT token工具类 5 ...

  5. SpringSecurity之整合JWT

    SpringSecurity之整合JWT 目录 SpringSecurity之整合JWT 1. 写在前面的话 2. JWT依赖以及工具类的编写 3. JWT过滤器 4. 登录成功结果处理器 5. Sp ...

  6. 基于SpringSecurity和JWT的用户访问认证和授权

    发布时间:2018-12-03   技术:springsecurity+jwt+java+jpa+mysql+mysql workBench   概述 基于SpringSecurity和JWT的用户访 ...

  7. Spring Security整合JWT,实现单点登录,So Easy~!

    前面整理过一篇 SpringBoot Security前后端分离,登录退出等返回json数据,也就是用Spring Security,基于SpringBoot2.1.4 RELEASE前后端分离的情况 ...

  8. SpringBoot + SpringSecurity + Mybatis-Plus + JWT实现分布式系统认证和授权

    1. 简介   Spring Security是一个功能强大且易于扩展的安全框架,主要用于为Java程序提供用户认证(Authentication)和用户授权(Authorization)功能.    ...

  9. SpringBoot + SpringSecurity + Mybatis-Plus + JWT + Redis 实现分布式系统认证和授权(刷新Token和Token黑名单)

    1. 前提   本文在基于SpringBoot整合SpringSecurity实现JWT的前提中添加刷新Token以及添加Token黑名单.在浏览之前,请查看博客:   SpringBoot + Sp ...

随机推荐

  1. js中Function引用类型中一些常见且有用的方法和属性

    Function类型 函数由于是Function类型的一个实例,所以函数名就是一个指向函数对象的指针,不会与某个函数死死的连接在一起,这也导致了js中没有真正的重载,但好处是,函数对象可以作为另一个函 ...

  2. LInux基础(04)项目设计一(理解链表管理协议的代码架构)

    要设计好一个项目必须要有一个健全的代码框架 一个结构体内有数据域和处理数据的函数指针, 先实现管理链表的函数 增加节点  删除节点  清空链表  遍历节点对每个节点进行操作 再实现协议的注册 把对象s ...

  3. C语言return返回值深入理解

    C语言使用return关键字返回函数值,可以很好对函数做封装,此处的疑问是:函数内部创建的变量都是局部变量,即私有的,作用域就在函数之内,为什么却可以把值传给调用函数? 解释这个问题还需要从C语言调用 ...

  4. Codeforces Round #580 (Div. 1) A-E

    Contest Page A Tag:构造 将$a_i$看做一个无穷数列,$i > 2n$时$a_i = a_{i - 2n}$.设$sgn_i = \sum\limits_{j=i+1}^{i ...

  5. Linux 7 重置root密码

    在运维工作中经常会遇到不知道密码,密码遗忘,密码被他人修改过的情况,使用这种方式扫清你一切烦恼! 1.启动Linux系统,在出现引导界面时,按“e”键,进入内核编辑界面:2.找到有“linux16”的 ...

  6. 2、Shell命令学习笔记

    1.Shell命令行解释器 1.1 Shell命令解释器 Shell是一个特殊的应用程序,介于操作系统内核和用户之间,负责接收用户输入的操作指令(命令)并进行解释,将需要执行的操作传递给内核执行. 因 ...

  7. IQueryable,IEnumerable,IList区别

    IQueryable和IEnumerable都是延时执行(Deferred Execution)的,而IList是即时执行(Eager Execution)IQueryable和IEnumerable ...

  8. 关于MVC接收Ajax调用无法访问的问题

    例如:下面代码有两个int类型的参数,如果Ajax调用时没有在data属性中为其赋值,AJAX会出500异常. public JsonResult GetList(int pageIndex, int ...

  9. 一个多进程爬虫下载图片的demo

    import os,re import pickle import requests import random import time from bs4 import BeautifulSoup f ...

  10. Java自学-操作符 位操作符

    Java的位操作符 位操作符 在实际工作中使用并不常见. 示例 1 : 一个整数的二进制表达 位操作都是对二进制而言的,但是我们平常使用的都是十进制比如5. 而5的二进制是101. 所以在开始学习之前 ...