springboot+jjwt+security完美解决restful接口无状态鉴权

简介: 微服务大行其道的现在,如果我们还在用wsdl之类的提供接口,给人的感觉就会很low,虽然说不能为了炫技而炫技,但是既然restful接口已经越来越流行,必然有它的道理。

微服务大行其道的现在,如果我们还在用wsdl之类的提供接口,给人的感觉就会很low,虽然说不能为了炫技而炫技,但是既然restful接口已经越来越流行,必然有它的道理。
本文我们不讨论restful接口的好处,旨在解决使用restful时候的权限控制问题。

springboot本身已经提供了很好的spring security的支持,我们只需要实现(或者重写)一部分接口来实现我们的个性化设置即可。本文浅显易懂,没有深入原理(后面文章会将,有需要的小伙伴稍等等~~~)。
思路:
1.通过spring security做授权拦截操作
2.通过jwt根据用户信息生成token以供后面调用
3.将生成的token放到HttpServletResponse头信息中
4.使用的时候从response头中获取token放在request头中提交到后台做认证即可
5.默认超时时间10天

一、pom

惯例还是先上pom,因为pom可以很直观的看到本项目用了哪些东西,我这个项目使用了很多的包,这里贴出了核心的几个,其他大部分的都会自动引用。

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>

二、登录过滤器

我们采用倒推法,用到什么找什么,这也比较符合XP编程的思想,不写多(无)余(用)的代码,既然要做认证,很明显需要一个过滤器来处理所有需要拦截的请求。
UsernamePasswordAuthenticationFilter是security自己提供的过滤器,我们重写其中的成功方法(successfulAuthentication)来处理我们自己的逻辑,当然根据自己的情况,比如登录失败处理,重写(unsuccessfulAuthentication)即可。
1.成功回调中用到一个TokenAuthenticationHandler,即token认证处理类,该类的主要方法就是借用jwt的机制来生成token,以供后面登录授权使用。
2.往response头信息中放入参数为“Authorization”,值为“Bearer ”+token的值

package com.mos.eboot.tools.jwt;

import com.mos.eboot.tools.util.FastJsonUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter{
    
    static final String TOKEN_PREFIX = "Bearer";
    static final String HEADER_STRING = "Authorization";
    
    private AuthenticationSuccessHandler successHandler;

    public JWTLoginFilter() {
    }

    public JWTLoginFilter(AuthenticationManager authManager) {
        setAuthenticationManager(authManager);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException {
        TokenAuthenticationHandler tokenAuthenticationHandler = new TokenAuthenticationHandler();
        Object obj = auth.getPrincipal();
        if(obj != null) {
            UserDetails userDetails = (UserDetails)obj;
            String token = tokenAuthenticationHandler.generateToken(FastJsonUtils.toJSONNoConfig(userDetails));
            res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + token);
        }
        
        if(successHandler != null) {
            successHandler.onAuthenticationSuccess(req, res, auth);
        }
    }

    public void setSuccessHandler(AuthenticationSuccessHandler successHandler) {
        this.successHandler = successHandler;
    }
}

JWTAuthenticationToken

package com.mos.eboot.tools.jwt;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

import static java.util.Collections.emptyList;

public class JWTAuthenticationToken extends UsernamePasswordAuthenticationToken{
    
    private static final long serialVersionUID = 1L;
    
    public JWTAuthenticationToken(Object principal) {
        super(principal,null,emptyList());
    }

    @Override
    public Object getCredentials() {
        return super.getCredentials();
    }

    @Override
    public Object getPrincipal() {
        return super.getPrincipal();
    }

}

TokenAuthenticationHandler

package com.mos.eboot.tools.jwt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 小尘哥
 */
public class TokenAuthenticationHandler implements Serializable {

    private static final long serialVersionUID = 1L;

    private static final String CLAIM_KEY_CREATED = "created";
    private static final String CLAIM_KEY_SUBJECT = "subject";

    private static final String DEFAULT_SECRET = "eboot@secret";
    private static final Long DEFAULT_EXPIRATION = 864000L;

    private String secret = DEFAULT_SECRET;
    private Long EXPIRATION = DEFAULT_EXPIRATION;

    public TokenAuthenticationHandler() {

    }

    public String getSubjectFromToken(String token) {
        String subject;
        try {
            final Claims claims = getClaimsFromToken(token);
            subject = claims.get(CLAIM_KEY_SUBJECT).toString();
        } catch (Exception e) {
            subject = null;
        }
        return subject;
    }


    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + EXPIRATION * 1000);
    }

    public String generateToken(String subject) {
        Map<String, Object> claims = new HashMap<String, Object>();
        claims.put(CLAIM_KEY_CREATED, new Date());
        claims.put(CLAIM_KEY_SUBJECT, subject);
        return generateToken(claims);
    }

    String generateToken( Map<String, Object> claims) {
        return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, secret).compact();
    }

}

三、认证

从头信息中取出Authorization,然后解析出个人信息,如果个人信息不为空,则将个人信息加密后再放入授权域。

package com.mos.eboot.tools.jwt;

import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

public class JWTAuthenticationFilter extends GenericFilterBean {
    
    static final String HEADER_STRING = "Authorization";
    static final String TOKEN_PREFIX = "Bearer";
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        
        HttpServletRequest req = (HttpServletRequest)request;
        
        String token = req.getHeader(HEADER_STRING);
        if(StringUtils.isNotBlank(token) && token.startsWith(TOKEN_PREFIX)) {
            TokenAuthenticationHandler tokenAuthenticationHandler = new TokenAuthenticationHandler();
            String subject = tokenAuthenticationHandler.getSubjectFromToken(token.replace(TOKEN_PREFIX, ""));
            if(StringUtils.isNotBlank(subject)) {
                SecurityContextHolder.getContext().setAuthentication(new JWTAuthenticationToken(subject));
            }
        }
        filterChain.doFilter(request,response);
    }

}

四、调用

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable().authorizeRequests().antMatchers("/**").authenticated()
                .antMatchers(HttpMethod.POST, "/login").permitAll().anyRequest().permitAll().and()
                .addFilterBefore(loginFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

//注入登录校验类
    @Bean
    public JWTLoginFilter loginFilter() throws Exception {
        JWTLoginFilter loginFilter = new JWTLoginFilter(authenticationManager());
        loginFilter.setSuccessHandler(loginAuthenticationSuccessHandler);
        loginFilter.setAuthenticationFailureHandler((request, response, exception) -> {
            response.setContentType("application/json");
            response.getWriter().write(FastJsonUtils
                    .toJSONString(new ResultModel(ResultStatus.FAIL.getCode(), exception.getMessage())));
        });
        return loginFilter;
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService());
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        authenticationProvider.setHideUserNotFoundExceptions(false);
        return authenticationProvider;
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return new UserService();
    }

//重写密码加密方法
    @Bean
    public Md5PasswordEncoder passwordEncoder() {
        Md5PasswordEncoder passwordEncoder = new Md5PasswordEncoder();
        passwordEncoder.setIterations(1);
        return passwordEncoder;
    }

实现UserDetailsService 接口,定义自己的获取用户登录方法实现类

package com.mos.eboot.api.config.support;

import com.mos.eboot.api.platform.api.ISysUserService;
import com.mos.eboot.platform.entity.SysUser;
import com.mos.eboot.tools.result.ResultModel;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author 小尘哥
 */
@Service("userService")
public class UserService implements IUserService {

    @Resource
    private ISysUserService sysUserService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return sysUserService.getByUsername(username);
    }
}


基本以上就可以搞定基础的无状态鉴权了,如果需要更深入的了解,比如权限自定义等,请关注我后面的文章.......

目录
相关文章
|
2月前
|
存储 前端开发 Java
SpringBoot使用云端资源url下载文件的接口写法
在Spring Boot中实现从云端资源URL下载文件的功能可通过定义REST接口完成。示例代码展示了一个`FileDownloadController`,它包含使用`@GetMapping`注解的方法`downloadFile`,此方法接收URL参数,利用`RestTemplate`下载文件,并将文件字节数组封装为`ByteArrayResource`返回给客户端。此外,通过设置HTTP响应头,确保文件以附件形式下载。这种方法适用于从AWS S3或Google Cloud Storage等云服务下载文件。
272 7
|
1月前
|
Dubbo JavaScript Java
SpringBoot 调用外部接口的三种方式
SpringBoot不仅继承了Spring框架原有的特性,还简化了应用搭建与开发流程。在SpringBoot项目中,有时需要访问外部接口或URL。本文介绍三种不使用Dubbo的方式:一是利用原生`httpClient`发起请求;二是使用`RestTemplate`,支持GET和POST请求,包括`getForEntity`、`getForObject`及`postForEntity`等方法;三是采用`Feign`客户端简化HTTP请求,需引入相关依赖并在启动类上启用Feign客户端。这三种方式均能有效实现对外部服务的调用。
|
8天前
|
存储 数据采集 Java
Spring Boot 3 实现GZIP压缩优化:显著减少接口流量消耗!
在Web开发过程中,随着应用规模的扩大和用户量的增长,接口流量的消耗成为了一个不容忽视的问题。为了提升应用的性能和用户体验,减少带宽占用,数据压缩成为了一个重要的优化手段。在Spring Boot 3中,通过集成GZIP压缩技术,我们可以显著减少接口流量的消耗,从而优化应用的性能。本文将详细介绍如何在Spring Boot 3中实现GZIP压缩优化。
31 6
|
14天前
|
缓存 Java 应用服务中间件
随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架
【9月更文挑战第6天】随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架。Nginx作为高性能的HTTP反向代理服务器,常用于前端负载均衡,提升应用的可用性和响应速度。本文详细介绍如何通过合理配置实现Spring Boot与Nginx的高效协同工作,包括负载均衡策略、静态资源缓存、数据压缩传输及Spring Boot内部优化(如线程池配置、缓存策略等)。通过这些方法,开发者可以显著提升系统的整体性能,打造高性能、高可用的Web应用。
43 2
|
21天前
|
前端开发 小程序 Java
【规范】SpringBoot接口返回结果及异常统一处理,这样封装才优雅
本文详细介绍了如何在SpringBoot项目中统一处理接口返回结果及全局异常。首先,通过封装`ResponseResult`类,实现了接口返回结果的规范化,包括状态码、状态信息、返回信息和数据等字段,提供了多种成功和失败的返回方法。其次,利用`@RestControllerAdvice`和`@ExceptionHandler`注解配置全局异常处理,捕获并友好地处理各种异常信息。
125 0
【规范】SpringBoot接口返回结果及异常统一处理,这样封装才优雅
|
27天前
|
SQL Java 测试技术
SpringBoot单元测试快速写法问题之PorkService 接口中的 getPork 方法的作用如何解决
SpringBoot单元测试快速写法问题之PorkService 接口中的 getPork 方法的作用如何解决
|
1月前
|
小程序 JavaScript Java
微信小程序+SpringBoot接入后台服务,接口数据来自后端
这篇文章介绍了如何将微信小程序与SpringBoot后端服务进行数据交互,包括后端接口的编写、小程序获取接口数据的方法,以及数据在小程序中的展示。同时,还涉及到了使用Vue搭建后台管理系统,方便数据的查看和管理。
微信小程序+SpringBoot接入后台服务,接口数据来自后端
|
1月前
|
XML JSON API
RESTful API设计最佳实践:构建高效、可扩展的接口
【8月更文挑战第17天】RESTful API设计是一个涉及多方面因素的复杂过程。通过遵循上述最佳实践,开发者可以构建出更加高效、可扩展、易于维护的API。然而,值得注意的是,最佳实践并非一成不变,随着技术的发展和业务需求的变化,可能需要不断调整和优化API设计。因此,保持对新技术和最佳实践的关注,是成为一名优秀API设计师的关键。
|
1月前
|
存储 监控 Java
|
1月前
|
JavaScript 前端开发 Java
SpringBoot 引入 smart-doc 接口文档管理插件,以及统一接口返回,最后推送到 Torna,进行统一管理
本文介绍了如何在SpringBoot项目中整合smart-doc接口文档管理插件,实现接口文档的生成和统一管理,并展示了如何将文档推送到Torna接口文档管理系统进行进一步的集中管理。
102 0
SpringBoot 引入 smart-doc 接口文档管理插件,以及统一接口返回,最后推送到 Torna,进行统一管理