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

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对象是否为空对象,通常可以通过以下几种方式进行: 1. 使用 == 进行判断 我们可以使用 Java 中的双等号 “==” 运算符来判断一个对象是否为 null。如果对象为 null,则其值为 null,否则就是一个有效对象。 下面是一个示例代码: Object object = null; if (object == null) { Sys…

    Java 2023年5月26日
    00
  • Spring Data JPA查询方式及方法名查询规则介绍

    Spring Data JPA查询方式及方法名查询规则介绍 Spring Data JPA是Spring Framework提供的一种简化数据访问层的方式。它通过提供一系列接口和实现来简化开发人员对数据库的访问,提高了开发效率。 Spring Data JPA提供了多种查询方式,包括查询方法名、使用@Query注解定义查询语句、使用Criteria API等…

    Java 2023年5月20日
    00
  • JSP实现的简单分页显示效果代码

    下面就是关于如何实现JSP简单分页显示效果的完整攻略。 一、分页原理介绍 分页是指将大量数据拆分成若干个小的单元,分别显示在不同的页面上。通过这种方式来展示大量数据可以更加清晰和直观。实现分页需要考虑到以下因素: 每页显示的数据数量 总共要显示的数据量 当前页数据的起始位置 当前页数和总页数 二、实现分页的方法 在JSP中,常用的实现分页的方法有两种:使用J…

    Java 2023年6月15日
    00
  • mybatis动态SQL if的test写法及规则详解

    MyBatis动态SQL if的test写法及规则详解 概述 MyBatis作为优秀的ORM框架,支持动态SQL语句的编写,其中if标签是最为基础和灵活的标签,可以通过if标签来很好地实现条件语句。本文将详细讲解MyBatis中if标签的test写法及规则。 if标签 if标签用于判断是否满足某个条件,当条件为true时会执行if标签下的SQL语句,当条件为…

    Java 2023年5月20日
    00
  • Java 实现定时任务的三种方法

    以下是对“Java 实现定时任务的三种方法”的详细讲解: Java 实现定时任务的三种方法 在实际开发中,经常会遇到需要在固定时间或间隔时间内执行任务的情况,这时候需要使用定时任务来完成。Java 提供了很多种方式来实现定时任务,本文将介绍三种比较常用的方法。 一、使用 Timer/TimerTask 类实现定时任务 1.1 Timer/TimerTask …

    Java 2023年5月18日
    00
  • Java中的ArrayList类常用方法和遍历

    关于Java中的ArrayList类常用方法和遍历,以下是一份详细攻略: ArrayList简介 ArrayList是Java中的一种集合框架,用于存储元素列表,也就是一个动态数组。ArrayList允许我们随意添加、删除、访问列表中的元素,并且会在内部自动调整大小,此外,ArrayList类还提供了一些方便的方法用于操作列表中的元素。 常用方法 下面是Ar…

    Java 2023年5月26日
    00
  • jsp 使用request为页面添加静态数据的实例

    下面是“jsp 使用request为页面添加静态数据的实例”的完整攻略: 1. 简介 在JSP页面中,我们可以使用 request 对象将静态数据传递到页面中,以便进行动态显示。 2. 实现过程 2.1 准备工作 首先,我们需要准备一个 JSP 页面,用来接收静态数据并进行展示。例如: <!DOCTYPE html> <html> &…

    Java 2023年6月15日
    00
  • Java aop面向切面编程(aspectJweaver)案例详解

    Java AOP面向切面编程(AspectJ Weaver)案例详解 什么是AOP? AOP全称Aspect-Oriented Programming,即面向切面编程。它是一种基于OOP(Object-Oriented Programming,面向对象编程)的编程思想,用于解决模块化开发中横切关注点的问题,以通过对横切关注点进行抽象,实现系统各模块之间的解耦…

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