旧版SpringSecurity和JWT实现认证和授权

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了旧版SpringSecurity和JWT实现认证和授权相关的知识,希望对你有一定的参考价值。

参考技术A SpringSecurity 是一个强大的可高度定制的 认证 和 授权 框架,对于 Spring 应用来说它是一套 Web 安全标准。 SpringSecurity 注重于为Java应用提供认证和授权功能,像所有的 Spring 项目一样,它对自定义需求具有强大的扩展性。

JWT 是 JSON WEB TOKEN 的缩写,它是基于 RFC 7519 标准定义的一种可以安全传输的的 JSON 对象,由于使用了数字签名,所以是可信任和安全的。

JWT token 的格式: header.payload.signature
header 中用于存放签名的生成算法: "alg":"HS512"
payload 中用于存放 用户名 、 token 的生成时间和过期时间

signature 为以 header 和 payload 生成的签名,一旦 header 和 payload 被篡改,验证将失败

这是一个 JWT 的字符串

可以在该网站上获得解析结果: https://jwt.io/

用户调用登录接口,登录成功后获取到 JWT 的 token ;
之后用户每次调用接口都在 http 的 header 中添加一个叫 Authorization 的头,值为 JWT 的 token ;
后台程序通过对 Authorization 头中信息的解码及数字签名校验来获取其中的用户信息,从而实现认证和授权。

在 pom.xml 中添加项目依赖

用于生成和解析 JWT token 的工具类
相关方法说明:

在用户名和密码校验前添加的过滤器,如果请求中有jwt的token且有效,会取出token中的用户名,然后调用SpringSecurity的API进行登录操作。

第十一篇SpringSecurity基于JWT实现Token的处理

SpringSecurity基于JWT实现Token的处理

  前面介绍了手写单点登录和JWT的应用,本文结合SpringSecurity来介绍下在SpringBoot项目中基于SpringSecurity作为认证授权框架的情况下如何整合JWT来实现Token的处理。

一、认证思路分析

  SpringSecurity主要是通过过滤器来实现功能的!我们要找到SpringSecurity实现认证和校验身份的过滤器!

1.回顾集中式认证流程

用户认证
  使用 UsernamePasswordAuthenticationFilter过滤器中 attemptAuthentication方法实现认证功能,该过滤器父类中 successfulAuthentication方法实现认证成功后的操作。认证失败是在 unsuccessfulAuthentication

身份校验

  使用 BasicAuthenticationFilter 过滤器中 doFilterInternal方法验证是否登录,以决定能否进入后续过滤器。

2.分析分布式认证流程

用户认证
  由于分布式项目,多数是前后端分离的架构设计,我们要满足可以接受异步post的认证请求参数,需要修改UsernamePasswordAuthenticationFilter过滤器中attemptAuthentication方法,让其能够接收请求体。
  另外,默认successfulAuthentication方法在认证通过后,是把用户信息直接放入session就完事了,现在我们需要修改这个方法,在认证通过后生成token并返回给用户。
身份校验
  原来BasicAuthenticationFilter过滤器中doFilterInternal方法校验用户是否登录,就是看session中是否有用户信息,我们要修改为,验证用户携带的token是否合法,并解析出用户信息,交给SpringSecurity,以便于后续的授权功能可以正常使用。

二、具体实现

1.创建项目

  创建一个SpringBoot项目.引入必要的依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.bobo</groupId>
            <artifactId>security-jwt-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.80</version>
        </dependency>
	<dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>
    </dependencies>

2.JWT工具类

  引入前面创建的JWT的工具类。


import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.security.SignatureException;
import java.util.Calendar;
import java.util.Map;

public class JWTUtils 
    // 秘钥
    private static final String SING = "123qwaszx";

    /**
     * 生成Token  header.payload.sing 组成
     * @return
     */
    public static String getToken(Map<String,String> map)
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE,7); // 默认过期时间 7天
        JWTCreator.Builder builder = JWT.create();
        // payload 设置
        map.forEach((k,v)->
            builder.withClaim(k,v);
        );
        // 生成Token 并返回
        return builder.withExpiresAt(instance.getTime())
                .sign(Algorithm.HMAC256(SING));
    

    /**
     * 验证Token
     * @return
     *     DecodedJWT  可以用来获取用户信息
     */
    public static DecodedJWT verify(String token)
        // 如果不抛出异常说明验证通过,否则验证失败
        DecodedJWT verify = null;
        try 
            verify = JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
        catch (SignatureVerificationException e)
            e.printStackTrace();
        catch (AlgorithmMismatchException e)
            e.printStackTrace();
        catch (Exception e)
            e.printStackTrace();
        
        return verify;
    


3.用户实例

  创建用户的实例,添加必要的属性

@Data
public class UserPojo implements UserDetails 

    private Integer id;

    private String username;

    private String password;

    private Integer status;


    @JsonIgnore
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() 
        List<SimpleGrantedAuthority> auth = new ArrayList<>();
        auth.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        return auth;
    

    @Override
    public String getPassword() 
        return this.password;
    

    @Override
    public String getUsername() 
        return this.username;
    
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() 
        return true;
    
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() 
        return true;
    
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() 
        return true;
    
    @JsonIgnore
    @Override
    public boolean isEnabled() 
        return true;
    

4.UserService

  完成基于SpringSecurity的数据库认证。创建UserService接口并实现

public interface UserService extends UserDetailsService 


@Service
public class UserServiceImpl implements UserService 
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
        UserPojo userPojo = new UserPojo();
        if("zhang".equals(username))
            userPojo.setUsername("zhang");
            userPojo.setPassword("$2a$10$hbMJRuxJoa6kWcfeT7cNPOGdoEXm5sdfSm5DQtp//2cmCF0MHO8b6");
            return userPojo;
        
        return userPojo;
    

5.自定义认证过滤器

  在SpringSecurity中的认证是通过UsernamePasswordAuthenticationFilter来处理的,现在我们要通过JWT来处理,那么我们就需要重写其中几个处理的方法

5.1 认证的方法

  认证的逻辑还是走的UserService处理,但是我们需要自己来手动的调用认证逻辑。

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException 

        UserPojo sysUser = null;
        try 
            sysUser = JSON.parseObject(getJson(request), UserPojo.class);
         catch (IOException e) 
            e.printStackTrace();
        
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());
        this.setDetails(request, authRequest);
        return authenticationManager.authenticate(authRequest);
    

    public String getJson(HttpServletRequest request) throws IOException
        BufferedReader streamReader = new BufferedReader( new InputStreamReader(request.getInputStream(), "UTF-8"));
        StringBuilder sb = new StringBuilder();
        String inputStr;
        while ((inputStr = streamReader.readLine()) != null) 
            sb.append(inputStr);
        
        return sb.toString();
    

5.2 认证成功

  认证成功生成Token信息,并保存在响应的header头中。

@Override
    protected void successfulAuthentication(HttpServletRequest request
            , HttpServletResponse response
            , FilterChain chain
            , Authentication authResult) throws IOException, ServletException 
        // 生成Token信息
        Map<String,String> map = new HashMap<>();
        map.put("username",authResult.getName());
        Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
        List<String> list = new ArrayList<>();
        for (GrantedAuthority authority : authorities) 
            list.add(authority.getAuthority());
        
        map.put("roles", JSON.toJSONString(list));
        String token = JWTUtils.getToken(map);
        response.addHeader("Authorization","Bearer"+token);
        try 
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_OK);
            PrintWriter out = response.getWriter();
            Map<String,Object> resultMap = new HashMap();
            resultMap.put("code", HttpServletResponse.SC_OK);
            resultMap.put("msg", "认证通过!");
            out.write(JSON.toJSONString(resultMap));
            out.flush();
            out.close();
        catch (Exception outEx)
            outEx.printStackTrace();
        
    

5.3 认证失败

  认证失败会调用 unsuccess… 方法来处理,那么在这儿我们就需要直接响应了

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException 
        try 
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            PrintWriter out = response.getWriter();
            Map resultMap = new HashMap();
            resultMap.put("code", HttpServletResponse.SC_UNAUTHORIZED);
            resultMap.put("msg", "用户名或密码错误!");
            out.write(new ObjectMapper().writeValueAsString(resultMap));
            out.flush();
            out.close();
        catch (Exception outEx)
            outEx.printStackTrace();
        
    

完整代码:

package com.bobo.jwt.filter;

public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter 

    private  AuthenticationManager authenticationManager;
    public TokenLoginFilter(AuthenticationManager authenticationManager)
        this.authenticationManager = authenticationManager;
    

    /**
     * 具体认证的方法
     * @param request
     * @param response
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException 

        UserPojo sysUser = null;
        try 
            sysUser = JSON.parseObject(getJson(request), UserPojo.class);
         catch (IOException e) 
            e.printStackTrace();
        
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());
        this.setDetails(request, authRequest);
        return authenticationManager.authenticate(authRequest);
    

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException 
        try 
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            PrintWriter out = response.getWriter();
            Map resultMap = new HashMap();
            resultMap.put("code", HttpServletResponse.SC_UNAUTHORIZED);
            resultMap.put("msg", "用户名或密码错误!");
            out.write(new ObjectMapper().writeValueAsString(resultMap));
            out.flush();
            out.close();
        catch (Exception outEx)
            outEx.printStackTrace();
        
    

    public String getJson(HttpServletRequest request) throws IOException
        BufferedReader streamReader = new BufferedReader( new InputStreamReader(request.getInputStream(), "UTF-8"));
        StringBuilder sb = new StringBuilder();
        String inputStr;
        while ((inputStr = streamReader.readLine()) != null) 
            sb.append(inputStr);
        
        return sb.toString();
    



    /**
     * 登录成功后的处理
     * @param request
     * @param response
     * @param chain
     * @param authResult
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request
            , HttpServletResponse response
            , FilterChain chain
            , Authentication authResult) throws IOException, ServletException 
        // 生成Token信息
        Map<String,String> map = new HashMap<>();
        map.put("username",authResult.getName());
        Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
        List<String> list = new ArrayList<>();
        for (GrantedAuthority authority : authorities) 
            list.add(authority.getAuthority());
        
        map.put("roles", JSON.toJSONString(list));
        String token = JWTUtils.getToken(map);
        response.addHeader("Authorization","Bearer"+token);
        try 
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_OK);
            PrintWriter out = response.getWriter();
            Map<String,Object> resultMap = new HashMap();
            resultMap.put("code", HttpServletResponse.SC_OK);
            resultMap.put(以上是关于旧版SpringSecurity和JWT实现认证和授权的主要内容,如果未能解决你的问题,请参考以下文章

SpringSecurity - 整合JWT使用 Token 认证授权

第十一篇SpringSecurity基于JWT实现Token的处理

第十一篇SpringSecurity基于JWT实现Token的处理

SpringSecurity + jwt 实现登录认证

深入浅出Spring原理及实战「开发实战系列」SpringSecurity与JWT实现权限管控以及登录认证指南

SpringBoot技术专题「JWT技术专区」带你一同开发SpringSecurity整合JWT授权和认证实战指南