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日

相关文章

  • Mac环境下配置tomcat的步骤详解

    以下是 Mac 环境下配置 Tomcat 的完整攻略步骤: 一、下载 Tomcat 首先,进入 Apache 官网,下载适合你操作系统的 Tomcat 版本。下载地址:http://tomcat.apache.org/download 二、解压 Tomcat 下载完成后,将文件解压到你希望的目录。在这里以解压到 “/Applications/” 目录下为例 …

    Java 2023年5月20日
    00
  • Spring Security 表单登录功能的实现方法

    下面为您讲解Spring Security表单登录功能的实现方法: 1. 配置Spring Security 在pom文件中添加依赖: <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security…

    Java 2023年5月20日
    00
  • MyBatisPlus分页的同时指定排序规则说明

    在使用MyBatisPlus进行分页时,可以同时指定排序规则,以保证分页结果的正确性。下面详细讲解如何进行操作。 1.在Mapper接口中定义分页查询方法 首先在Mapper接口中定义分页查询方法,方法需要使用MyBatisPlus提供的IPage来进行分页操作,示例如下: public interface MyMapper extends BaseMapp…

    Java 2023年5月20日
    00
  • java基础详解之数据类型知识点总结

    Java基础详解之数据类型知识点总结 一、Java中的数据类型 Java中的数据类型可以分为两大类: 基本数据类型(Primitive Types):包括整型(byte、short、int、long)、浮点型(float、double)、字符型(char)和布尔型(boolean)四种。 引用数据类型(Reference Types):包括类、接口、数组等。…

    Java 2023年5月26日
    00
  • 浅谈一段java代码是如何执行的

    下面我将给您详细讲解“浅谈一段java代码是如何执行的”的完整攻略。该攻略主要分为以下4个步骤: Java程序的编译过程 Java程序的运行过程 JVM对Java程序的运行过程的支持 示例说明 1. Java程序的编译过程 Java程序的编译过程分为以下三个步骤: 编写源代码:将编写的Java程序保存到以.java为后缀名的文本文件中。 编译源代码:使用ja…

    Java 2023年5月30日
    00
  • Spring源码:Bean的生命周期(二)

    前言 让我们继续讲解Spring的Bean实例化过程。在上一节中,我们已经讲解了Spring是如何将Bean定义加入到IoC容器中,并使用合并的Bean定义来包装原始的Bean定义。接下来,我们将继续讲解Spring的 getBean() 方法,特别是针对 FactoryBean 的解析。 在 getBean() 方法中,Spring还支持对 Factory…

    Java 2023年5月1日
    00
  • Spring Boot实现热部署的五种方式

    Spring Boot是一个快速开发框架,可以帮助开发人员快速构建Web应用程序。在开发过程中,经常需要修改代码并重新编译,这会浪费很多时间。为了提高开发效率,Spring Boot提供了热部署功能,可以在不重启应用程序的情况下实时更新代码。本文将介绍Spring Boot实现热部署的五种方式,并提供两个示例。 方式一:使用Spring Boot DevTo…

    Java 2023年5月15日
    00
  • Java数据库连接池连接Oracle过程详解

    Java数据库连接池连接Oracle过程详解 本文将详细讲解Java数据库连接池连接Oracle的过程,包括连接池的作用、如何配置连接池、连接池连接Oracle的步骤、注意事项等。 连接池的作用 连接池是为了提高系统性能和稳定性而设计的。在Java中,使用连接池可以避免频繁地打开和关闭数据库连接,从而节省系统资源。当一个请求需要访问数据库时,连接池会从连接池…

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