介绍 自己尝试在基于 spring-boot 的 RESTFul API 的安全认证上做的调研与测试。 微服务架构是未来的趋势,但是在 API 安全认证方面的文章不多,大多数是基于 MVC 和 session 的,但是微服务时代的 API 都是无状态的,遵循 Restful( 附一篇比较好的博客介绍 RESTFul 理 ..

spring-security-jwt Restful API Token 安全认证

本贴最后更新于 432 天前,其中的信息可能已经渤澥桑田

介绍

自己尝试在基于 spring-boot 的 RESTFul API 的安全认证上做的调研与测试。

微服务架构是未来的趋势,但是在 API 安全认证方面的文章不多,大多数是基于 MVC 和 session 的,但是微服务时代的 API 都是无状态的,遵循 Restful( 附一篇比较好的博客介绍 RESTFul 理解 RESTful 架构 )约束。不能满足需求,所以我就推动一下,将文章弄新一点。

适合读者

为什么不采用 httpBasic

由于基于 httpBasic 认证的很简单,网上教程一大堆,不做过多介绍。这里我采用的是基于 jwt 协议的 API 安全认证。

用户每次都在传输过程中传输用户名和密码,太不安全了

用户每次请求一个 url 都会访问数据库,会导致数据库的访问压力!太大了。

为什么不采用 OAuth2

OAuth2 协议也是十分优秀的,但是我还是个菜鸟,虽然理解了 OAuth2 ,实现感觉也不难,但是还米有看懂 spring-boot 的框架源码,所以在集成方面不是很顺手。

其实还有一点就是 OAuth2 相对 jwt 等来说还是相对复杂的。如果我们不对外提供提供授权服务,name 使用 OAuth 个人感觉还是相当费劲的,传输效率和模式上,都没有 jwt 等 token 来的方便。

其实,基于 token 的认证,大多数自己实现也是十分方便的,加密解密,存在 Redis 里,然后自动过期,一切都很简单,大家自己设计适合自己的认证才是最关键的。

实现

基于 MySQL 的用户认证

网上代码一大堆,其实就是继承 UserDetailsService 就行了,需要自己设计 jpa 的表,我的代码里有例子,可以随意扩展。

@Service
public class UserService implements UserDetailsService {
    @Autowired
    SysUserRepository sysUserRepository;

然后在 WebSecurityConfigurerAdapter 中进行配置自己的 UserDetailsService,很简单。


@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    UserDetailsService customUserService() {
        return new UserService();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserService());
    }

jwt 的 token 生成

就是 jwt 协议的 加密和解密。需要设置加密算法,加密秘钥,过期时间等。

加密的时候,把 username 加进去,讲道理应该是 userid。这样我们前端就可以使用该 id 来进行请求其他服务。

解密的时候,需要进行过期校验,秘钥校验等


class TokenAuthenticationService {
    static final long EXPIRATIONTIME = 1000*60*60*24*1; // 1 days
    static final String SECRET = "ThisIsASecret";
    static final String TOKEN_PREFIX = "Bearer";
    static final String HEADER_STRING = "Authorization";

    static void addAuthentication(HttpServletResponse res, String username) {
        String JWT = Jwts.builder()
                .setSubject(username)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATIONTIME))
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .compact();
        res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + JWT);
    }

    static Authentication getAuthentication(HttpServletRequest request) {
        String token = request.getHeader(HEADER_STRING);
        if (token != null) {
            // parse the token.
            String user = Jwts.parser()
                    .setSigningKey(SECRET)
                    .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                    .getBody()
                    .getSubject();

            return user != null ?
                    new UsernamePasswordAuthenticationToken(user, null, emptyList()) :
                    null;
        }
        return null;
    }
}

jwt 的 filter 进行 rul 认证

这里设置 登录 只能从 post 进行,并设置了两个 filter 分别对 login 和其他 url 进行拦截。

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers(HttpMethod.POST, "/login").permitAll()
                .anyRequest().authenticated()
                .and()
                // We filter the api/login requests
                .addFilterBefore(new JWTLoginFilter("/login", authenticationManager()),
                        UsernamePasswordAuthenticationFilter.class)
                // And filter other requests to check the presence of JWT in header
                .addFilterBefore(new JWTAuthenticationFilter(),
                        UsernamePasswordAuthenticationFilter.class);
    }

如何使用

使用之前

由于我集成了 spring-data-jpa,所以在使用之前需要配置数据库,并插入一些数据。

还好,我在初始化工程的时候替你们做了。我在 init 目录中进行了初始化设置,所以会自动插入两个用户名。

@Override
    public void run(String... args) throws Exception {
        logger.info(">>>>>>>>>>>>>>>服务启动检查1,开始检查用户系统<<<<<<<<<<<<<");

        if (sysUserRepository.count() != 0) {
            logger.info(">>>>>>>>>>>>>>>用户已经初始化<<<<<<<<<<<<<");
        } else {
            logger.info(">>>>>>>>>>>>>>不存在初始用户,开始创建<<<<<<<<<<<<<");
            SysRole sysRole1 = new SysRole(1L, "ROLE_ADMIN");
            SysRole sysRole2 = new SysRole(2L, "ROLE_USER");
            sysRoleRepository.save(Arrays.asList(sysRole1, sysRole2));

            List<SysRole> roles1 = Arrays.asList(sysRole1);
            SysUser sysUser1 = new SysUser(1L, "root", "nibudong");
            sysUser1.setRoles(roles1);

            List<SysRole> roles2 = Arrays.asList(sysRole2);
            SysUser sysUser2 = new SysUser(2L, "shilei", "nicai");
            sysUser2.setRoles(roles2);

            sysUserRepository.save(Arrays.asList(sysUser1, sysUser2));
            logger.info(">>>>>>>>>>>>>>初始用户创建结束<<<<<<<<<<<<<");
        }

        logger.info(">>>>>>>>>>>>>>>检查用户系统结束<<<<<<<<<<<<<");

    }

如何使用

  1. 先获得 jwt 的 token
  2. 再请求 url

代码

代码在的 GitHub 里,https://github.com/xjtushilei/spring-boot-security

最后

登录请求一定要使用 HTTPS,否则无论 Token 做的安全性多好密码泄露了也是白搭

  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    693 引用 • 1263 回帖 • 746 关注
  • RESTful

    一种软件架构设计风格而不是标准,提供了一组设计原则和约束条件,主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

    24 引用 • 103 回帖 • 90 关注
  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    10 引用 • 10 回帖 • 3 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    2558 引用 • 7953 回帖 • 828 关注
1 回帖
请输入回帖内容...