java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot与JWT和Shiro整合

Spring Boot与JWT和Shiro整合实践步骤(非常详细!)

作者:拉米医生

这篇文章主要介绍了Spring Boot与JWT和Shiro整合的相关资料,将Shiro、Spring Boot和JWT集成在一起,可以大大提高应用程序的安全性,文中通过代码介绍的非常详细,需要的朋友可以参考下

1. Spring Boot基础介绍

在现代企业级应用开发中,Spring Boot凭借其轻量级、快速开发的特性,已经成为构建微服务架构项目的首选框架。本章将为读者提供Spring Boot的入门级介绍,旨在帮助读者快速掌握Spring Boot的基础知识,并了解其在项目中的具体应用。

1.1 Spring Boot简介

Spring Boot是Spring的一个模块,它提供了快速构建项目的能力。通过“约定优于配置”的原则,Spring Boot旨在简化Spring应用的初始搭建以及开发过程。它通过提供默认配置,帮助开发者避免繁琐的配置工作,从而专注于业务逻辑的实现。

1.2 Spring Boot的核心特性

1.3 入门示例

一个典型的Spring Boot应用的入口是一个带有 main 方法的类,通常包含 SpringApplication.run 调用:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

pom.xml 文件中添加Spring Boot的依赖,可以快速开始:

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

以上是一个简单的Spring Boot应用的搭建过程,接下来的内容将深入探讨如何将Spring Boot与其他技术栈整合,以实现更为复杂的应用场景。

2. JWT认证机制解析

2.1 JWT的基本概念和组成

2.1.1 JWT的定义和作用

JSON Web Token(JWT)是一种开放标准(RFC 7519),用于在互联网环境中安全地传输信息。它作为一种紧凑的、自包含的方式,使得双方可以以JSON对象的形式安全传递声明(claims)。JWT通常用于身份验证和信息交换,特别适用于Web API的认证场景。

在Web应用中,一旦用户登录成功,服务器会生成一个JWT并返回给客户端,之后客户端将该JWT存储在客户端的浏览器或移动设备中。客户端每次向服务器发送请求时,都需要在HTTP请求的头信息中附带该JWT。服务器通过解析JWT中的信息,就可以验证用户的身份和访问权限。

2.1.2 JWT的结构和特点

JWT由三部分组成:Header(头部)、Payload(载荷)和Signature(签名)。这三部分通过点(.)连接在一起,形成一个完整的JWT字符串。

{
  "alg": "HS256",
  "typ": "JWT"
}
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

2.2 JWT的工作原理和优势

2.2.1 认证流程详解

JWT的认证流程一般如下:

  1. 用户登录到应用系统,并提供用户名和密码。
  2. 应用系统验证用户信息的正确性。
  3. 验证通过后,应用系统创建一个JWT,将用户的身份信息、权限信息和其他声明打包到Payload中。
  4. 应用系统对JWT的Header和Payload部分使用Base64Url编码,并使用密钥对它们进行签名。
  5. 将生成的JWT返回给用户,用户将JWT存储在本地(例如浏览器的localStorage或sessionStorage中)。
  6. 用户在之后的请求中,将JWT作为Authorization header附带在每个HTTP请求中。
  7. 服务器收到请求后,提取JWT并验证其签名,确认其有效性和未被篡改。
  8. 如果JWT验证成功,服务器根据其中的声明信息进行用户身份的识别,并授权访问资源。

2.2.2 JWT相较于传统认证的优势

JWT相较于传统的Session认证机制,具有以下优势:

2.3 JWT的安全性分析

2.3.1 JWT的加密和签名机制

JWT通过Header和Payload的Base64Url编码后与签名组合而成。签名是为了确保JWT没有被篡改,其安全性依赖于密钥的保密性。

2.3.2 JWT的安全漏洞及防范措施

JWT虽然方便,但也存在一些潜在的安全风险:

接下来,我们深入探讨JWT的加密和签名机制以及如何实现和优化这些机制,确保系统的安全稳定运行。

3. Shiro安全框架功能概览

Shiro是Apache软件基金会的一个开源安全框架,为应用程序提供认证、授权、加密和会话管理功能。本章节将深入探讨Shiro的核心架构和组件,以及其在企业级应用中的实践。

3.1 Shiro核心架构和组件

3.1.1 Shiro的架构组成

Shiro的架构设计简洁而灵活,由三个主要部分组成:Subject、SecurityManager和Realms。

3.1.2 主要组件功能解析

Shiro框架还包含其他组件,如 Authentication , Authorization , Session Management , Cryptography 等。这里我们将对每个组件的功能进行分析。

3.2 Shiro的认证与授权机制

3.2.1 用户认证流程和策略

Shiro提供了多种认证策略,其中基于表单的认证是最常见的。用户输入用户名和密码,Shiro会对这些信息进行验证。下面是Shiro的认证流程:

  1. 用户提交用户名和密码。
  2. Shiro的 Subject 将认证请求提交给 SecurityManager
  3. SecurityManager 将请求委托给 AuthenticationStrategy (认证策略)。
  4. AuthenticationStrategy 可以进行多个Realm认证(即多重认证)。
  5. 每个Realm将认证请求转换成领域对象,并传递给相应的用户数据源。
  6. 如果所有Realm的认证通过,则认证成功,否则失败。

3.2.2 权限管理原理和实践

权限管理是Shiro的核心功能之一,它负责控制用户访问资源的能力。Shiro使用 Roles (角色)和 Permissions (权限)概念来控制用户访问。

Shiro中的权限表示可以非常细粒度,例如:

String permission = "user:create"; // 创建用户的权限

通过在应用程序中设置相应的角色和权限,Shiro的授权机制能够有效地管理用户访问。

3.3 Shiro在企业级应用中的实践

3.3.1 Shiro与Spring Boot的整合

在企业级应用中,Shiro与Spring Boot的整合是常见的需求。以下为整合步骤的简介:

  1. 添加依赖 : 在pom.xml文件中添加Spring Boot和Shiro的依赖。

    xml <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.5.3</version> </dependency>
  2. 创建配置类 : 创建一个配置类来配置Shiro的Bean。

    java @Configuration public class ShiroConfig { @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 配置Realms等... return securityManager; } // 其他Bean配置... }
  3. 创建Realm : 自定义一个Realm来连接你的用户数据源。

    java @Service public class MyRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 授权逻辑... } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 认证逻辑... } }

3.3.2 Shiro在微服务架构中的应用案例

Shiro可以与Spring Cloud微服务架构很好地协同工作。以下是在微服务架构中应用Shiro的一个案例:

通过这些实践,Shiro能够支持从单体应用到分布式微服务架构中的企业级应用需求。在下一章中,我们将详细探讨如何配置依赖、如何使用JWT工具类以及如何设置过滤器链和异常处理策略。

4. 整合步骤详述

4.1 配置依赖和Shiro配置

4.1.1 添加Spring Boot和Shiro的依赖

在Spring Boot项目中整合Shiro和JWT认证机制,首先需要在 pom.xml 文件中添加相关的依赖。对于Spring Boot项目,通常还需要添加Spring Boot Starter的依赖,以方便项目的构建和管理。以下是添加Shiro和JWT相关依赖的示例代码:

<!-- Spring Boot Starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

<!-- Shiro -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.7.1</version>
</dependency>

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

确保所添加的Shiro版本与Spring Boot版本兼容,并根据项目需要选择合适的JWT库版本。

4.1.2 Shiro的配置文件详解

application.properties application.yml 配置文件中,可以进行Shiro的配置。下面是一个简单的配置示例:

# Shiro 配置
shiro.ini=classpath:shiro.ini

对于更复杂的配置,我们可能需要定义 shiro.ini 文件或使用Spring配置类来配置Shiro。例如,在 shiro.ini 中定义安全策略和认证信息:

[main]
# 配置自定义的Realm
myRealm=com.example.MyRealm

# 设置SecurityManager的Realm
securityManager.realms=$myRealm

# 配置会话管理器
sessionManager=org.apache.shiro.web.session.mgt.DefaultWebSessionManager
securityManager.sessionManager=$sessionManager

# JWT认证过滤器配置
authc=org.apache.shiro.web.filter.authc.FormAuthenticationFilter
jwtAuthc=org.example.MyJWTFilter

# 将自定义的JWT认证过滤器加入Shiro过滤器链
[urls]
# 登录请求不拦截
/login=authc
# 其他请求需要通过JWT认证
/**=jwtAuthc

对于安全性要求更高的场景,可以使用Java配置类来替代 shiro.ini 文件:

@Bean
public SecurityManager securityManager() {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(myRealm());
    securityManager.setSessionManager(sessionManager());
    return securityManager;
}

@Bean
public SessionManager sessionManager() {
    DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    // 自定义会话ID生成器
    sessionManager.setSessionIdGenerator(sessionIdGenerator());
    return sessionManager;
}

@Bean
public MyJWTFilter jwtFilter() {
    return new MyJWTFilter();
}

在配置过程中,要注意权限管理和认证策略的匹配,以及会话管理的合理设置。对JWT的处理则更多依赖于自定义的过滤器来实现。

4.2 JWT工具类实现

4.2.1 JWT的生成和解析

JWT的生成和解析是实现JWT认证机制的核心步骤。我们需要创建一个工具类来处理这些操作。以下是一个简单的JWT工具类实现示例:

public class JWTUtil {
    private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
    private static final String ISSUE_ON_KEY = "issueOn";
    private static final String EXP_KEY = "exp";

    public static String createJWT(String subject, String issuer, String secret, long ttlMillis) {
        // 获取当前时间
        Date now = new Date();
        // 获取过期时间
        Date expTime = new Date(now.getTime() + ttlMillis);
        // 获取JWT ID
        String jwtId = IdUtils.simpleUUID();

        // 创建JWT
        JwtBuilder builder = Jwts.builder()
                .setId(jwtId)
                .setIssuedAt(now)
                .setSubject(subject)
                .setIssuer(issuer)
                .signWith(SIGNATURE_ALGORITHM, secret);

        // 添加过期时间
        if (expTime != null) {
            builder.setExpiration(expTime);
        }

        return builder.compact();
    }

    public static Claims parseJWT(String jwt, String secret) {
        return Jwts.parser()
                .setSigningKey(secret.getBytes())
                .parseClaimsJws(jwt)
                .getBody();
    }
}

在这个工具类中, createJWT 方法用于生成JWT, parseJWT 方法用于解析JWT。 SIGNATURE_ALGORITHM 定义了签名算法,确保了JWT的安全性。在实际使用中,应根据实际业务需求对JWT的内容和过期时间进行定制。

4.2.2 JWT工具类的封装

为了提高工具类的可用性和安全性,我们还需要对其进行适当的封装,使其易于在应用程序中使用。以下是一个封装示例:

public class JWTUtil {
    // ... JWT的生成和解析方法

    // 获取当前用户的方法
    public static String getCurrentUser(String jwt, String secret) {
        Claims claims = parseJWT(jwt, secret);
        return claims.getSubject();
    }
}

在这里,我们添加了 getCurrentUser 方法,该方法通过解析JWT获取当前用户信息。这样在业务逻辑中,我们就可以通过JWT直接获取当前登录用户的信息,而无需每次都解析JWT。

4.3 过滤器链设置和异常处理

4.3.1 Shiro的Filter链配置

Shiro的过滤器链(Filter Chain)允许我们以声明式的方式配置过滤器,拦截特定请求并进行相应的处理。通过配置Shiro的 [urls] 部分,可以设置哪些请求需要进行认证或授权。这里是一个配置示例:

@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(securityManager);

    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    filterChainDefinitionMap.put("/login", "authc"); // 登录请求,不进行JWT认证
    filterChainDefinitionMap.put("/**", "jwtAuthc"); // 其他请求进行JWT认证
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

    return shiroFilterFactoryBean;
}

配置完成后,所有进入 /** 的请求都需要通过名为 jwtAuthc 的过滤器进行处理,此过滤器为自定义的JWT认证过滤器。

4.3.2 常见异常处理策略

在使用Shiro进行认证和授权时,可能会遇到各种异常情况,比如无效的认证信息、未授权访问等。Shiro提供了默认的异常处理器 DefaultWebSecurityManager ,但通常我们会自定义异常处理策略来提供更好的用户体验。以下是一个异常处理器的示例:

public class MyExceptionHandler implements ExceptionMapper<Exception> {
    @Override
    public String toHTML(Exception e) {
        // 根据异常类型返回不同的错误信息
        if (e instanceof AuthorizationException) {
            return "<p>您没有权限访问此资源。</p>";
        } else if (e instanceof AuthenticationException) {
            return "<p>认证失败,请重新登录。</p>";
        } else {
            return "<p>服务器异常,请稍后再试。</p>";
        }
    }
}

通过实现 ExceptionMapper 接口,我们可以在自定义的JWT认证过滤器中使用这个异常处理器来返回给用户更加友好的错误提示。

4.4 权限控制的实现细节

4.4.1 自定义权限验证

在实际项目中,通常需要根据不同的业务需求实现自定义的权限验证逻辑。在Shiro中,这通常是通过实现 Authorizer 接口或使用 Realm 来完成的。以下是一个简单的 Realm 实现示例:

public class MyRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 从principals中获取当前用户的principal信息
        String username = (String) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // 根据用户名查询用户权限信息,然后添加到authorizationInfo中
        List<String> permissions = getPermissionsByUsername(username);
        authorizationInfo.addStringPermissions(permissions);
        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 这里实现用户的登录验证逻辑,通常会根据token信息查询数据库验证用户名和密码
        // 如果验证通过,返回一个SimpleAuthenticationInfo对象
        // 如果验证失败,可以抛出相应的AuthenticationException异常
    }
}

doGetAuthorizationInfo 方法中,我们根据用户的身份信息(通常是用户名)来查询用户所拥有的权限,并将其封装到 AuthorizationInfo 对象中。然后,Shiro会根据这些权限信息进行授权。

4.4.2 权限控制与用户界面的结合

实现权限控制后,还需要在用户界面中正确地展示或限制用户权限。这通常在控制器层或者服务层通过调用Shiro的API来完成。以下是一个简单的权限控制示例:

@Controller
public class MyController {
    @RequestMapping("/admin")
    public String adminPage(Principal principal) {
        // 只有拥有admin权限的用户才能访问此页面
        if (SecurityUtils.getSubject().isPermitted("admin")) {
            return "admin";
        } else {
            return "accessDenied";
        }
    }
}

在这个示例中,只有当用户拥有 admin 权限时,才能访问 admin 页面,否则会跳转到 accessDenied 页面。通过调用 SecurityUtils.getSubject().isPermitted() 方法,我们可以检查用户是否拥有特定的权限。

以上内容为第四章整合步骤的详述,从配置Shiro和JWT,到权限控制和用户界面的结合,为读者提供了一个整合这些技术点的细致指南。在下一章节,我们将通过实际项目的代码演示整合过程,使读者更易理解和应用这些技术。

5. 通过实际项目代码演示整合过程

5.1 项目结构和代码概览

5.1.1 项目整体结构布局

在进行实际项目整合的演示之前,我们首先需要了解项目的基本结构。假设我们已经有了一个基于Spring Boot和Maven的项目结构,如下所示:

.
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── example
│   │   │           └── myproject
│   │   │               ├── MyProjectApplication.java
│   │   │               ├── config
│   │   │               │   ├── SecurityConfig.java
│   │   │               │   └── ShiroConfig.java
│   │   │               ├── controller
│   │   │               │   ├── UserController.java
│   │   │               │   └── ResourceController.java
│   │   │               ├── service
│   │   │               │   ├── UserService.java
│   │   │               │   └── ResourceService.java
│   │   │               ├── model
│   │   │               │   ├── User.java
│   │   │               │   └── Resource.java
│   │   │               └── repository
│   │   │                   ├── UserRepository.java
│   │   │                   └── ResourceRepository.java
│   │   └── resources
│   │       ├── application.properties
│   │       └── static
│   └── test
│       └── java
│           └── com
│               └── example
│                   └── myproject
│                       └── MyProjectApplicationTests.java
└── pom.xml

5.1.2 关键代码片段解读

ShiroConfig.java 中,我们将配置Shiro的默认安全策略,包括设置自定义的Realm和启用注解支持:

@Configuration
public class ShiroConfig {

    @Bean
    public SecurityManager securityManager(Realm realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }

    @Bean
    public Realm realm() {
        return new CustomRealm();
    }
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        shiroFilter.setLoginUrl("/login");
        shiroFilter.setUnauthorizedUrl("/403");
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/logout", "logout");
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilter;
    }
    // Other beans and configurations...
}

5.2 功能模块的实现

5.2.1 用户登录模块

UserController.java 中,我们将编写处理用户登录请求的逻辑:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
        // Authenticate user
        User user = userService.authenticateUser(loginRequest.getUsername(), loginRequest.getPassword());
        if (user != null) {
            // Create JWT token and send it back to the user
            String token = JwtUtil.createToken(user);
            return ResponseEntity.ok(new JwtResponse(token));
        } else {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid credentials");
        }
    }
}

5.2.2 资源访问控制模块

ResourceController.java 中,我们将演示如何使用Shiro注解来控制资源访问权限:

@RestController
@RequestMapping("/resources")
public class ResourceController {

    @GetMapping("/{id}")
    @RequiresPermissions("resource:view")
    public ResponseEntity<?> viewResource(@PathVariable Long id) {
        // Fetch the resource and return it
        Resource resource = resourceService.findById(id);
        return ResponseEntity.ok(resource);
    }
    @PostMapping("/create")
    @RequiresRoles("admin")
    public ResponseEntity<?> createResource(@RequestBody Resource resource) {
        // Create a new resource
        Resource createdResource = resourceService.createResource(resource);
        return ResponseEntity.ok(createdResource);
    }
}

5.3 测试和调试

5.3.1 单元测试的编写与执行

为了确保我们的整合工作正常运行,编写单元测试是至关重要的。在 MyProjectApplicationTests.java 中,我们可以使用Mockito来模拟服务层的行为,确保我们的控制器层逻辑正确:

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyProjectApplicationTests {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    public void testLoginSuccess() throws Exception {
        User mockUser = new User(...);
        when(userService.authenticateUser(anyString(), anyString())).thenReturn(mockUser);
        mockMvc.perform(post("/user/login")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"username\":\"user\", \"password\":\"pass\"}"))
                .andExpect(status().isOk());
    }
}

5.3.2 调试过程中遇到的问题及解决方案

在测试过程中,我们可能遇到了 JwtUtil.createToken 方法无法调用的情况,原因可能是依赖注入没有正确配置。在 JwtUtil 类上添加 @Component 注解,并确保JWT相关的配置类已经注册到Spring容器中,可以解决这个问题。

5.4 安全性和性能优化

5.4.1 常见安全问题排查与加固

为了加固安全性,我们需要排查和解决以下问题:

5.4.2 性能优化策略及实施

对于性能优化,我们可以采取以下策略:

通过这些策略,我们能够确保我们的应用在安全性得到保障的同时,也能够应对高并发场景的需求。

总结

到此这篇关于Spring Boot与JWT和Shiro整合实践的文章就介绍到这了,更多相关SpringBoot与JWT和Shiro整合内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文