基于springboot和redis实现单点登录

接下来我将详细讲解“基于springboot和redis实现单点登录”的完整攻略,过程中将包含两个示例说明。

1. 基础知识

1.1 什么是单点登录?

单点登录(Single Sign-On,简称SSO)是一种允许用户使用一个帐户和密码访问多个应用程序的技术。在实现SSO时,用户只需要在一个应用程序中进行身份验证,然后就可以访问其他应用程序,而无需再次输入身份验证凭据。

1.2 为什么要使用redis?

在实现SSO时,我们需要共享用户的登录状态信息,以便在多个应用程序中使用。为了实现这一点,我们可以使用Cookie或Session,但是当应用程序数量增加时,这种方法可能在性能和安全方面受到挑战。因此,我们可以使用分布式缓存来存储状态信息,并使其在多个应用程序之间共享。Redis是一个被广泛使用的开源、基于内存的分布式缓存解决方案,提供快速读写能力和良好的可扩展性。

1.3 使用技术

在此攻略中,我们将使用以下技术:

  • SpringBoot:一种基于Spring框架的快速开发应用程序的技术。
  • Redis:一个基于内存的开源分布式键值对存储系统。
  • Spring Security:一个在Spring应用程序中提供安全性身份验证和授权的框架。
  • JWT:一种用于客户端-服务器通信的开放标准,用于在Web应用程序中传输信息作为JSON对象。

2. 示例1:单个应用程序上的实现

在这个示例中,我们将演示如何在单个应用程序中实现基于Spring Boot和Redis的单点登录。

2.1 创建SpringBoot项目

首先,我们需要使用SpringBoot创建一个新的项目,具体操作可以使用IDE工具(如IntelliJ IDEA)或者使用Spring官网进行创建。在创建过程中需要引入Spring Security和Redis的依赖。

2.2 配置Redis连接

接下来,我们需要创建一个Redis连接,并将其配置为Spring应用程序的默认缓存管理器。在Spring Boot中,我们可以通过配置文件轻松完成此操作。在application.properties文件中添加以下配置:

# Redis连接配置
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database=0

2.3 创建登录页面

接下来,我们需要创建一个登录页面,让用户在此处输入用户名和密码。可以使用标准HTML表单和Spring Security进行身份验证。在此示例中,我们假设用户在登录成功后将被重定向到主页。

<!DOCTYPE html>
<html>
<head>
    <title>登录</title>
</head>
<body>
    <h1>登录</h1>
    <form method="post" action="/login">
        <!--请输入用户名:<input type="text" name="username" required><br>-->
        <!--请输入密码:<input type="password" name="password" required><br>-->
        请输入用户名:<input type="text" name="username" required>
        <br>
        请输入密码:<input type="password" name="password" required>
        <br>
        <button type="submit">登录</button>
    </form>
</body>
</html>

2.4 实现身份验证和创建Token

然后,我们需要实现身份验证并创建Token,使用Spring Security进行身份验证。在这个例子中,用户名和密码将被存储在内存数据库(后面的章节中会展示如何使用数据库进行身份验证),并且在验证成功后,用户将被重定向到主页,并得到一个JWT Token。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // username和password存储在内存数据库中
        auth
            .inMemoryAuthentication()
                .withUser("user")
                .password("password")
                .roles("USER");
    }

    @Bean
    public TokenProvider tokenProvider() {
        return new TokenProvider();
    }
}

public class TokenProvider {
    private static final Logger logger = LoggerFactory.getLogger(TokenProvider.class);

    private final String secretKey;
    private final long tokenValidityInSeconds;

    public TokenProvider() {
        this.secretKey = "mySecretKey";
        this.tokenValidityInSeconds = 3600;
    }

    public String createToken(String username) {
        Date now = new Date();
        Date validity = new Date(now.getTime() + this.tokenValidityInSeconds * 1000);

        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(now)
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .setExpiration(validity)
                .compact();
    }

    public Authentication getAuthentication(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token)
                .getBody();

        List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");

        User principal = new User(claims.getSubject(), "", authorities);

        return new UsernamePasswordAuthenticationToken(principal, "", authorities);
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
            return true;
        } catch (SignatureException ex) {
            logger.error("Invalid JWT signature");
        } catch (MalformedJwtException ex) {
            logger.error("Invalid JWT token");
        } catch (ExpiredJwtException ex) {
            logger.error("Expired JWT token");
        } catch (UnsupportedJwtException ex) {
            logger.error("Unsupported JWT token");
        } catch (IllegalArgumentException ex) {
            logger.error("JWT claims string is empty.");
        }
        return false;
    }
}

2.5 存储Token

现在,我们需要将Token存储到Redis缓存中,以便将Token共享到其他应用程序中。在这个例子中,我们在用户登录成功后创建Token,并将其存储到Spring默认的CacheManager中。CacheManager本质上就是一个Spring容器,用于管理Spring Cache抽象的缓存和缓存管理器的工厂。

@Controller
public class LoginController {

    private TokenProvider tokenProvider;
    private CacheManager cacheManager;

    public LoginController(TokenProvider tokenProvider, CacheManager cacheManager) {
        this.tokenProvider = tokenProvider;
        this.cacheManager = cacheManager;
    }

    @PostMapping("/login")
    public String login(HttpServletRequest request, HttpServletResponse response) {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        Authentication authentication = new UsernamePasswordAuthenticationToken(username, password);

        try {
            authentication = new TokenProvider().authenticate(authentication);
            SecurityContextHolder.getContext().setAuthentication(authentication);

            String token = tokenProvider.createToken(username);
            cacheManager.getCache("tokens").put(token, username);

            CookieUtils.addCookie(response, "token", token, 1800);
            return "redirect:/";
        } catch (BadCredentialsException e) {
            return "redirect:/login?error";
        }
    }
}

2.6 配置CacheManager

现在,我们需要将Spring默认的CacheManager替换为使用Redis作为缓存的管理器。我们可以通过向Spring应用程序添加RedisCacheManager bean来实现这一点。在这个例子中,我们将Redis缓存的过期时间设置为30分钟,以确保Token不会一直保留在Redis缓存中。

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30L))
            .disableCachingNullValues();

    return RedisCacheManager.builder(redisConnectionFactory)
            .cacheDefaults(redisCacheConfiguration)
            .transactionAware()
            .build();
}

示例2:多个应用程序上的实现

在这个示例中,我们将扩展第一个示例并演示如何在多个应用程序中实现基于Spring Boot和Redis的单点登录。

2.7 创建多个应用程序

首先,我们需要创建多个SpringBoot应用程序来充当我们的多个应用程序。在这个示例中,我们将创建两个应用程序:sso-client1和sso-client2。

2.8 修改配置文件

接下来,我们需要修改这些应用程序的application.properties文件,以使用Redis作为缓存管理器。我们还需要配置这些应用程序允许哪个地址作为已经登录用户的重定向地址:

# Redis连接配置
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database=0

# SSO配置
sso.redirect.url=http://localhost:{server.port}
sso.include.urls=/, /homepage

2.9 创建拦截器

然后,我们需要创建一个拦截器来截获要访问的URL,并检查用户是否已登录。如果用户已登录,则拦截器将允许请求继续,并将用户信息注入请求中。否则,拦截器将将请求重定向到登录页面。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private TokenProvider tokenProvider;
    private RestTemplate restTemplate;
    private String redirectUrl;
    private String[] includeUrls;

    public SecurityConfig(TokenProvider tokenProvider, RestTemplate restTemplate,
            @Value("${sso.redirect.url}") String redirectUrl, @Value("${sso.include.urls}") String[] includeUrls) {
        this.tokenProvider = tokenProvider;
        this.restTemplate = restTemplate;
        this.redirectUrl = redirectUrl;
        this.includeUrls = includeUrls;
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .antMatchers("/health")
                .antMatchers("/info")
                .antMatchers("/css/**")
                .antMatchers("/js/**")
                .antMatchers("/img/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        SsoInterceptor ssoInterceptor = new SsoInterceptor(tokenProvider, restTemplate, redirectUrl, includeUrls);

        http.addFilterBefore(new SsoFilter(ssoInterceptor), BasicAuthenticationFilter.class)
            .authorizeRequests()
                .antMatchers(includeUrls).authenticated();
    }
}

public class SsoInterceptor implements HandlerInterceptor {
    private TokenProvider tokenProvider;
    private RestTemplate restTemplate;
    private String redirectUrl;
    private String[] includeUrls;

    public SsoInterceptor(TokenProvider tokenProvider, RestTemplate restTemplate,
            String redirectUrl, String[] includeUrls) {
        this.tokenProvider = tokenProvider;
        this.restTemplate = restTemplate;
        this.redirectUrl = redirectUrl;
        this.includeUrls = includeUrls;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = CookieUtils.getCookie(request, "token");

        if (StringUtils.isEmpty(token) || !tokenProvider.validateToken(token)) {
            String returnUrl = request.getRequestURL().toString();
            response.sendRedirect(redirectUrl + "/login?returnUrl=" + URLEncoder.encode(returnUrl, "UTF-8"));
            return false;
        }

        String username = (String) cacheManager.getCache("tokens").get(token, String.class).get();
        if (StringUtils.isEmpty(username)) {
            response.sendRedirect(redirectUrl + "/login?error");
            return false;
        }

        // 设置用户身份信息
        List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
        User principal = new User(username, "", authorities);
        Authentication authentication = new UsernamePasswordAuthenticationToken(principal, "", authorities);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        return true;
    }
}

public class SsoFilter extends OncePerRequestFilter {
    private SsoInterceptor ssoInterceptor;

    public SsoFilter(SsoInterceptor ssoInterceptor) {
        this.ssoInterceptor = ssoInterceptor;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        ssoInterceptor.preHandle(request, response, null);

        filterChain.doFilter(request, response);
    }
}

2.10 作为标识源发布令牌

最后,我们需要创建一个标识源项目,该项目用于将令牌发布给其他应用程序。在这个示例中,我们将运行标识源应用程序并发布Token,然后添加到其他应用程序的Cookie中。当用户从一个应用程序访问另一个应用程序时,应用程序将检查Token,并在用户已登录的情况下提供对应用程序的访问权限。

@RestController
public class SsoController {
    private TokenProvider tokenProvider;

    public SsoController(TokenProvider tokenProvider) {
        this.tokenProvider = tokenProvider;
    }

    @GetMapping("/singleSignOn/token")
    public String getToken(@RequestParam String username) {
        return tokenProvider.createToken(username);
    }
}

至此,我们已经成功实现了基于Spring Boot和Redis的单点登录。感谢阅读这篇攻略,希望它能够对您有所帮助!

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:基于springboot和redis实现单点登录 - Python技术站

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

相关文章

  • sql 函数大全 比较常用的一些函数整理第1/2页

    首先,我们需要了解什么是SQL函数。在SQL中,函数是一些特殊的操作符,它们接受一些参数,执行特定的计算,并返回一个结果。SQL函数可以用于执行日期和时间处理、字符串操作、数学计算等一系列操作。本文将整理比较常用的SQL函数,让您更快更方便地查询、计算数据。 SQL 函数大全:比较常用的一些函数整理(第1/2页) 1. 字符串函数 常用的字符串函数包括: 1…

    database 2023年5月21日
    00
  • DBMS中BCNF和4NF的区别

    BCNF和4NF是数据库设计中的两个重要的范式,它们都是通常用来规范化表结构以避免冗余和数据异常发生的工具。但是,BCNF和4NF的优先级不同,使用场景也不同。 一、BCNF BCNF(Boyce-Codd范式)是数据库设计中的一种规范化范式,在满足3NF(第三范式)的基础上,再进一步规范化数据表。BCNF是非正规化问题的一种解决方案。 在满足3NF的基础上…

    database 2023年3月27日
    00
  • nodejs简单实现操作arduino

    这里给您详细讲解一下“nodejs简单实现操作arduino”完整攻略。 介绍 随着物联网的发展,越来越多的设备被连接到互联网上。在这些设备中,通过arduino连接各种传感器是很常见的一种应用。而在nodejs中,我们可以通过一些库去操作串口连接arduino板,从而通过nodejs读写arduino中的传感器数据。 步骤 1. 硬件准备 首先,我们需要准…

    database 2023年5月22日
    00
  • 初步认知MySQL metadata lock(MDL)

    概述 随着5.5.3引入MDL,更多的Query被“Waiting for table metadata lock”给’炕’了SHOW PROCESSLIST的输出也有之前的”Locked”变得粒度更加细的’Waiting for table metadata lock’引入MDL,当需要访问、修改表结构时,都需要对元数据上锁(读/写)MDL在Server层…

    MySQL 2023年4月13日
    00
  • Linux 集群技术

    Linux 集群技术详解 什么是 Linux 集群技术? Linux 集群技术是利用多台计算机(通常是服务器)组成一个集群,以达到提高系统可用性、可扩展性、负载均衡等目的。这些计算机之间通过网络通信进行交互,并共享数据和资源。 常见的 Linux 集群技术 Linux 集群技术有很多种,常见的包括: 1. 负载均衡集群 负载均衡集群的目的是将请求分布到多台服…

    database 2023年5月22日
    00
  • python将MongoDB里的ObjectId转换为时间戳的方法

    要将MongoDB中的ObjectId转换为时间戳,可以使用Python的bson(Binary JSON)库中的ObjectId对象。具体步骤如下: 安装bson库:使用pip命令在终端安装bson库。 pip install bson 导入bson库和datetime库:在Python代码中导入bson库和datetime库。 import bson f…

    database 2023年5月22日
    00
  • MySQL执行外部sql脚本文件的命令

    MySQL执行外部SQL脚本文件的命令主要是通过mysql客户端工具来完成的,具体步骤如下: 打开终端或命令行工具,输入以下命令登录MySQL服务器: mysql -h 主机名 -u 用户名 -p 其中,主机名为MySQL数据库所在服务器的IP地址或域名,用户名为MySQL数据库的管理员账户名,-p参数表示输入密码(输完密码后按回车键)。 进入MySQL命令…

    database 2023年5月22日
    00
  • 一文带你了解MySQL中的事务

    一文带你了解 MySQL 中的事务 什么是事务? 事务是指作为单个逻辑工作单元执行的一系列操作。这些操作必须全部执行或者全部不执行,如果其中有任何一个操作失败,则整个事务都必须回滚到起始状态。 MySQL 中的事务 MySQL 中的事务是通过 commit 和 rollback 语句进行控制的。commit 语句用于提交事务,将进行的所有更改保存到数据库中,…

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