Spring Security基于JWT实现SSO单点登录详解

yizhihongxing

Spring Security基于JWT实现SSO单点登录详解

什么是单点登录(SSO)?

单点登录(SSO)指的是用户只需要一次登录,就可以访问多个应用系统。在传统的系统中,我们需要为每一个系统单独注册,单独登录,对于用户来说,这是一种不便。

JWT是什么?

JWT(JSON Web Token)是一种用于身份验证的开放标准。它是由 IETF(Internet 工程任务组)制定的 RFC 7519。JWT主要用于应用之间的身份验证和授权,它通过加密来保护信息的传输过程中的安全性。

Spring Security与JWT的配合使用

Spring Security是一个功能强大的安全框架,可以帮助我们实现应用程序和用户之间的安全交互。Spring Security可以与多种身份认证和授权机制配合使用,其中JWT是目前最流行的一种。

实现思路

本文的实现思路如下:

  1. 用户在访问系统A时,输入用户名和密码,系统A根据用户名和密码生成JWT,将JWT返回给用户;
  2. 用户需要访问系统B时,将在请求头中添加JWT信息并发送请求给系统B;
  3. 系统B解析JWT,验证用户的身份,并将用户访问系统B的信息返回给用户。

实现步骤

1. 引入相关依赖

pom.xml文件中,添加以下依赖:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>1.1.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

2. 创建配置类

Config包中,创建一个JwtConfig类,用于配置JWT的密钥、过期时间等信息。

@Configuration
public class JwtConfig {

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private Long expiration;

    public String getSecret() {
        return secret;
    }

    public Long getExpiration() {
        return expiration;
    }

}

其中 @Value 注解用于获取配置文件中的属性值。

3. 创建过滤器

Filter包中,创建一个JwtTokenFilter类,用于拦截请求并验证JWT信息。

public class JwtTokenFilter extends OncePerRequestFilter {

    @Autowired
    private JwtConfig jwtConfig;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        String header = request.getHeader(jwtConfig.getHeader());

        if (StringUtils.isBlank(header) || !header.startsWith(jwtConfig.getTokenHead())) {
            chain.doFilter(request, response);
            return;
        }

        String token = header.replace(jwtConfig.getTokenHead(), "");
        try {
            Claims claims = Jwts.parser()
                .setSigningKey(jwtConfig.getSecret())
                .parseClaimsJws(token)
                .getBody();

            String username = claims.getSubject();
            if (StringUtils.isNotBlank(username) && SecurityContextHolder.getContext().getAuthentication() == null) {
                User principal = new User(username, "", new ArrayList<>());

                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(principal, null, principal.getAuthorities());

                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        } catch (Exception e) {
            logger.error("Token 验证失败:" + e.getMessage());
            SecurityContextHolder.clearContext();
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            response.getWriter().write(e.getMessage());
            return;
        }

        chain.doFilter(request, response);
    }

}

其中包括获取JWT Token的信息、验证Token并设置认证信息的过程。

4. 配置Spring Security

WebSecurityConfig类中,添加以下内容:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtConfig jwtConfig;

    @Autowired
    private JwtTokenFilter jwtTokenFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and().authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 解决跨域问题
            .antMatchers("/").permitAll()
            .antMatchers("/auth/**").permitAll()
            .anyRequest().authenticated();

        // 添加 JWT 过滤器
        http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

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

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}

其中包括配置Session、过滤器链、安全策略等内容。

5. 创建JWT工具类

Utils包中,创建一个JwtTokenUtils类,用于生成、验证JWT。

public class JwtTokenUtils {

    public static final String TOKEN_HEADER = "Authorization";
    public static final String TOKEN_PREFIX = "Bearer ";

    public static String generateToken(String username, String secret, Date expireDate) {
        Map<String, Object> map = new HashMap<>();
        map.put("alg", "HS256");
        map.put("typ", "JWT");

        String token = Jwts.builder()
            .setHeader(map)
            .setSubject(username)
            .setIssuedAt(new Date())
            .setExpiration(expireDate)
            .signWith(SignatureAlgorithm.HS256, secret)
            .compact();

        return token;
    }

    public static Claims getClaimByToken(String token, String secret) {
        try {
            return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static boolean isTokenExpired(Date expiration) {
        return expiration.before(new Date());
    }

}

其中包括生成Token、解析Token、验证Token等方法。

示例1:基于JWT的身份认证

UserController中添加以下内容:

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

    @Autowired
    private UserService userService;

    @Autowired
    private JwtConfig jwtConfig;

    @PostMapping("/login")
    public String login(@RequestParam String username, @RequestParam String password) {
        if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
            return "用户名或密码不能为空";
        }

        User user = userService.getUserByUsername(username);
        if (user == null || !new BCryptPasswordEncoder().matches(password, user.getPassword())) {
            return "用户名或密码错误";
        }

        String token = JwtTokenUtils.generateToken(username, jwtConfig.getSecret(), new Date(System.currentTimeMillis() + jwtConfig.getExpiration() * 1000));

        return "Bearer " + token;
    }

}

其中包括用户登录逻辑,生成JWT并返回前端。

示例2:基于JWT的单点登录

AdminController中添加以下内容:

@RestController
@RequestMapping("/admin")
public class AdminController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello, admin!";
    }

}

UserController中添加以下内容:

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

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/hello")
    public String hello(@RequestHeader(AUTHORIZATION) String token) {
        HttpHeaders headers = new HttpHeaders();
        headers.set(AUTHORIZATION, token);

        HttpEntity<String> entity = new HttpEntity<>(headers);
        ResponseEntity<String> responseEntity = restTemplate.exchange("http://localhost:8081/admin/hello", HttpMethod.GET, entity, String.class);

        return responseEntity.getBody();
    }

}

其中包括调用AdminController的逻辑,获取JWT Token,并在请求头中添加。

至此,基于JWT的单点登录就实现了。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring Security基于JWT实现SSO单点登录详解 - Python技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • Java 泛型详解(超详细的java泛型方法解析)

    Java泛型详解 什么是泛型? 泛型主要体现在类和方法中,用于实现在编译时期进行类型检查和类型推断的功能,从而避免了在运行时出现类型转换的错误。 泛型类 泛型类是指在类的定义中使用了泛型,即类中的属性、方法等都可以使用泛型。泛型类的语法格式如下: class ClassName<T1, T2, …> { //属性的类型也可以使用泛型 T1 a…

    Java 2023年5月23日
    00
  • ajax详解_动力节点Java学院整理

    AJAX详解 什么是AJAX AJAX(Asynchronous JavaScript and XML)即异步 JavaScript 和 XML,是一种在Web页面中实现异步数据交互的通信技术。它的核心是 XMLHttpRequest 对象,它可以在不刷新页面的情况下发送和接收数据。 AJAX的优点 页面无需刷新,数据实时更新 能够异步加载数据,减少页面加载…

    Java 2023年5月26日
    00
  • 关于SpringMVC对Restful风格的支持详解

    关于SpringMVC对Restful风格的支持详解 在Web开发中,RESTful风格的API设计已经成为了一种趋势。SpringMVC作为一个流行的Web框架,也提供了对RESTful风格的支持。本文将详细讲解SpringMVC对RESTful风格的支持,包括如何使用@RequestMapping注解、如何使用@PathVariable注解、如何使用@R…

    Java 2023年5月18日
    00
  • Java 数据结构与算法系列精讲之数组

    Java 数据结构与算法系列精讲之数组 数组的定义和基本操作 数组是一种线性数据结构,它由一系列相同类型的元素组成,这些元素在内存中连续存储。 定义 在Java中定义数组需要指定数据类型和数组长度,例如: int[] arr = new int[10]; // 定义一个长度为10的整型数组 基本操作 数组的基本操作包括了以下几个方面: 初始化:默认初始化为类…

    Java 2023年5月19日
    00
  • java解一个比较特殊的数组合并题

    我将为您讲解如何解决一个比较特殊的Java数组合并题。我将分为以下步骤进行讲解: 确定题目要求:根据题目要求,我们需要实现一个方法,用于将两个有序数组合并为一个大的有序数组。 确定解题思路:我们可以使用双指针的方式来解决这个问题,具体思路如下: 1) 我们定义三个指针:p1指向第一个数组的开头,p2指向第二个数组的开头,p3指向新数组的开头。 2) 比较p1…

    Java 2023年5月26日
    00
  • IDEA全局查找关键字的用法解读

    下面就为大家详细讲解“IDEA全局查找关键字的用法解读”的完整攻略。 1. 什么是IDEA全局查找 IDEA全局查找是指在IDEA中查找某个关键字时,不仅可以在当前文件中查找,还可以在整个项目中查找。 2. 如何使用IDEA全局查找 使用IDEA全局查找非常简单,具体步骤如下: 打开需要查找的项目。 在菜单栏中点击“Edit” -> “Find” -&…

    Java 2023年6月15日
    00
  • Java 实战项目锤炼之在线美食网站系统的实现流程

    Java 实战项目锤炼之在线美食网站系统的实现流程 1. 确定需求 在项目启动前,首先要仔细理解用户的需求。针对在线美食网站系统,我们需要明确以下问题: 网站需要提供哪些功能,例如用户注册、登录、浏览餐厅、下单、支付等 网站需要支持哪些业务特性,例如搜索、推荐、评价等 网站需要支撑多少用户量,需要考虑如何做好服务器部署和负载均衡 网站的安全性需要考虑哪些问题…

    Java 2023年5月19日
    00
  • Sprint Boot @NotBlank使用方法详解

    以下是关于Spring Boot中@NotBlank的作用与使用方法的完整攻略,包含两个示例: @NotBlank的作用 @NotBlank是Spring Boot提供的一个注解,用于验证字符串类型的请求参数是否为空或空格。它可以用于验证请求参数的有效性,以确保用程序的正确性和安全性。 @NotBlank的使用方法 以下是使用@NotBlank的示例: 验证…

    Java 2023年5月5日
    00
合作推广
合作推广
分享本页
返回顶部