SpringSecurity+OAuth2实现单点登录SSO(详细教程+源码)

喜欢ヅ旅行 2023-05-31 12:17 142阅读 0赞

文章目录

    • 一、父级项目sso-oauth2-demo
    • 二、授权服务器auth-server
    • 三、客户端应用client-a与client-b
    • 四、启动与测试

本节源码在https://github.com/laolunsi/spring-boot-examples上,请放心食用

本节利用Spring Security Oauth2实现SpringBoot项目的单点登录功能。

创建三个SpringBoot应用:auth-server, client-a, client-b,其中auth-server是授权服务器,用于登录、获取用户信息。

本节采用SpringBoot 2.1.9.RELEASE和Spring security 2.1.9.RELEASE


一、父级项目sso-oauth2-demo

首先我们创建一个父maven项目,取名sso-oauth2-demo。而上面说的三个应用是这个项目的子应用。父级项目maven引入如下配置:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>com.example</groupId>
  5. <artifactId>sso-oauth2-demo</artifactId>
  6. <version>1.0-SNAPSHOT</version>
  7. <properties>
  8. <java.version>1.8</java.version>
  9. <spring-boot.version>2.1.9.RELEASE</spring-boot.version>
  10. <spring-security.version>2.1.9.RELEASE</spring-security.version>
  11. </properties>
  12. <dependencies>
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-security</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework.security.oauth.boot</groupId>
  19. <artifactId>spring-security-oauth2-autoconfigure</artifactId>
  20. <version>${spring-security.version}</version>
  21. </dependency>
  22. </dependencies>
  23. <dependencyManagement>
  24. <dependencies>
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-dependencies</artifactId>
  28. <version>${spring-boot.version}</version>
  29. <scope>import</scope>
  30. <type>pom</type>
  31. </dependency>
  32. </dependencies>
  33. </dependencyManagement>
  34. </project>

这个父级项目只有一个maven的pom.xml,不需要代码。下面我们开始创建授权服务器和客户端应用A/B


二、授权服务器auth-server

在这个sso-oauth2-demo项目下创建子SpringBoot项目——auth-server,引入如下依赖配置:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <parent>
  5. <groupId>com.example</groupId>
  6. <artifactId>sso-oauth2-demo</artifactId>
  7. <version>1.0-SNAPSHOT</version>
  8. <relativePath/> <!-- lookup parent from repository -->
  9. </parent>
  10. <artifactId>auth-server</artifactId>
  11. <version>0.0.1-SNAPSHOT</version>
  12. <name>auth-server</name>
  13. <description>Demo project for Spring Boot</description>
  14. <dependencies>
  15. <dependency>
  16. <groupId>org.springframework.boot</groupId>
  17. <artifactId>spring-boot-starter-web</artifactId>
  18. </dependency>
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-starter-test</artifactId>
  22. <scope>test</scope>
  23. </dependency>
  24. </dependencies>
  25. <build>
  26. <plugins>
  27. <plugin>
  28. <groupId>org.springframework.boot</groupId>
  29. <artifactId>spring-boot-maven-plugin</artifactId>
  30. </plugin>
  31. </plugins>
  32. </build>
  33. </project>

这里我们可以看到是继承了父级的sso-oauth2-demo项目,从父级项目继承了一些公共的依赖。

配置:

  1. server:
  2. port: 8300
  3. servlet:
  4. context-path: '/auth'

PS:授权服务器的配置文件比较简单,不需要其他东西。

启动类:

  1. @SpringBootApplication
  2. @EnableResourceServer
  3. public class AuthServerApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(AuthServerApplication.class, args);
  6. }
  7. }

PS: 启用资源服务器

下面需要进行一些关于auth和security的配置,这里需要两个类:

  1. /** * 授权服务器配置 */
  2. @Configuration
  3. @EnableAuthorizationServer
  4. public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
  5. @Autowired
  6. private BCryptPasswordEncoder passwordEncoder;
  7. @Override
  8. public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
  9. oauthServer.tokenKeyAccess("permitAll")
  10. .checkTokenAccess("isAuthenticated()");
  11. }
  12. @Override
  13. public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
  14. clients.inMemory()
  15. .withClient("SampleClientId")
  16. .secret(passwordEncoder.encode("secret"))
  17. .authorizedGrantTypes("authorization_code")
  18. .scopes("user_info")
  19. .autoApprove(true)
  20. .redirectUris("http://localhost:8301/login", "http://localhost:8302/login");
  21. }
  22. // 必须进行redirectUris的配置,否则请求授权码时会报错:error="invalid_request", error_description="At least one redirect_uri must be registered with the client."
  23. }

PS:BCryptPasswordEncoder如果注入不了,可以直接尝试new。

  1. /** * security基本配置 * 本demo中,登录用户名与密码是固定的,实际项目中应该从数据库读取 */
  2. @Configuration
  3. @Order(1)
  4. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  5. @Override
  6. protected void configure(HttpSecurity http) throws Exception {
  7. http.requestMatchers()
  8. .antMatchers("/login", "/oauth/authorize")
  9. .and()
  10. .authorizeRequests()
  11. .anyRequest()
  12. .authenticated()
  13. .and()
  14. .formLogin()
  15. .permitAll()
  16. .and().csrf().disable();
  17. }
  18. @Override
  19. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  20. auth.inMemoryAuthentication()
  21. .withUser("eknown")
  22. .password(passwordEncoder().encode("123"))
  23. .roles("USER");
  24. }
  25. @Bean
  26. public BCryptPasswordEncoder passwordEncoder() {
  27. return new BCryptPasswordEncoder();
  28. }
  29. }

到这一步,授权服务器的基本配置已经完成了,下面我们需要提供一个获取登陆用户信息的接口:

  1. /** * 该接口类中的唯一接口,用于ClientA和ClientB在登录成功后获取用户信息用 * 该接口地址可以任意修改,只要与ClientA/B中配置的用户信息地址一致即可 */
  2. @RestController
  3. @RequestMapping(value = "user")
  4. public class UserAction {
  5. @GetMapping(value = "me")
  6. public Principal me(Principal principal) {
  7. System.out.println("调用me接口获取用户信息:" + principal);
  8. return principal;
  9. }
  10. }

PS:上述接口将交由ClientA和ClientB使用,在配置中指明,用于登录后获取当前的用户信息。

好了,下面我们创建两个测试应用——client-a和client-b

三、客户端应用client-a与client-b

创建父级maven的子SpringBoot项目——client-a和client-b,这里仅展示client-a的创建和配置过程,而client-b仅是名字不同而已。

修改maven文件:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <parent>
  5. <groupId>com.example</groupId>
  6. <artifactId>sso-oauth2-demo</artifactId>
  7. <version>1.0-SNAPSHOT</version>
  8. <relativePath/> <!-- lookup parent from repository -->
  9. </parent>
  10. <artifactId>client-a</artifactId>
  11. <version>0.0.1-SNAPSHOT</version>
  12. <name>client-a</name>
  13. <description>Demo project for Spring Boot</description>
  14. <dependencies>
  15. <dependency>
  16. <groupId>org.springframework.boot</groupId>
  17. <artifactId>spring-boot-starter-web</artifactId>
  18. </dependency>
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-starter-test</artifactId>
  22. <scope>test</scope>
  23. </dependency>
  24. <!-- 引入thymeleaf和thymeleaf security的依赖 -->
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.thymeleaf.extras</groupId>
  31. <artifactId>thymeleaf-extras-springsecurity5</artifactId>
  32. </dependency>
  33. </dependencies>
  34. <build>
  35. <plugins>
  36. <plugin>
  37. <groupId>org.springframework.boot</groupId>
  38. <artifactId>spring-boot-maven-plugin</artifactId>
  39. </plugin>
  40. </plugins>
  41. </build>
  42. </project>

配置,这里较为复杂,务必注意:

  1. server:
  2. port: 8301
  3. servlet:
  4. session:
  5. cookie:
  6. name: CLIENT_A_SESSION
  7. security:
  8. oauth2:
  9. client:
  10. client-id: SampleClientId
  11. client-secret: secret
  12. access-token-uri: http://localhost:8300/auth/oauth/token
  13. user-authorization-uri: http://localhost:8300/auth/oauth/authorize
  14. resource:
  15. user-info-uri: http://localhost:8300/auth/user/me # 从授权服务器获取当前登录用户信息的地址
  16. spring:
  17. thymeleaf:
  18. cache: false

PS:上面的user-info-uri与之前的auth-server中的接口对应上了!

启动类不需要修改。下面需要一个security的配置类:

  1. @EnableOAuth2Sso
  2. @Configuration
  3. public class MySecurityConfig extends WebSecurityConfigurerAdapter {
  4. @Override
  5. public void configure(HttpSecurity http) throws Exception {
  6. http.antMatcher("/**")
  7. .authorizeRequests()
  8. .antMatchers("/", "/login**")
  9. .permitAll()
  10. .anyRequest()
  11. .authenticated();
  12. }
  13. }

到这一步,基本配置已经完成了。下面我们来创建测试页面和接口:

在resources文件夹下——即application.yml的同级目录下,创建一个templaes文件夹, 并放入两个页面:

index.html:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  5. <title>Spring Security SSO</title>
  6. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" />
  7. </head>
  8. <body>
  9. <div class="container">
  10. <div class="col-sm-12">
  11. <h1>Spring Security SSO 客户端A</h1>
  12. <a class="btn btn-primary" href="securedPage">Login</a>
  13. </div>
  14. </div>
  15. </body>
  16. </html>

securedPage.html:

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  5. <title>Spring Security SSO</title>
  6. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" />
  7. </head>
  8. <body>
  9. <div class="container">
  10. <div class="col-sm-12">
  11. <h1>Secured Page, Client A</h1>
  12. Welcome, <span th:text="${#authentication.name}">Name</span>
  13. </div>
  14. </div>
  15. </body>
  16. </html>

PS:index.html是不需要验证的,而securedPage或使用authentication.name,所以需要授权。

下面我们创建获取页面的接口:

  1. /** * 获取页面的接口 */
  2. @Controller
  3. public class IndexAction {
  4. @GetMapping(value = "")
  5. public String index() {
  6. System.out.println("进入ClientA首页");
  7. return "index.html";
  8. }
  9. @GetMapping(value = "securedPage")
  10. public String home() {
  11. System.out.println("进入ClientA securedPage");
  12. return "securedPage.html";
  13. }
  14. }

然后按照上面的方式,同样步骤创建client-b项目,注意修改一些client-a和client-b相关的名称或配置。

创建完毕后,我们就可以进入测试阶段了!


四、启动与测试

启动这三个项目,分别运行在8300/8301/8302三个端口上。

首先我们访问http://localhost:8301,进入到index.html:

\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ocVdD1Ii-1571929781007)(04-SpringBoot单点登录.assets/1571908861322.png)\]

点击login,注意这个接口仅仅是打开了securedPage.html,而由于securedPage.html使用了需要进行授权的用户信息,会oauth2自动重定向到auth-server对应的http://localhost:8300/auth/login页面了:

\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fxBAOceu-1571929781007)(04-SpringBoot单点登录.assets/1571908899209.png)\]

输入默认的用户名eknow和密码123进行登录:

\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gBDB4Y7K-1571929781008)(04-SpringBoot单点登录.assets/1571908984867.png)\]

下面打开client-b的首页:

\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aBpIj1JQ-1571929781008)(04-SpringBoot单点登录.assets/1571909103350.png)\]

点击login后,发现并没有重定向到auth-server的登录页面,而是获取到了用户数据,进入了clientB的secured页面,这表示单点登录成功了!

也就是ClientA登录成功后,位于同一浏览器上的ClientB应用,自动进行了授权验证操作,不需要再次登录了!

\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UdGjJUTq-1571929781009)(04-SpringBoot单点登录.assets/1571909122437.png)\]

好了,到这一步,我们的sso-oauth2-demo项目已经完成了!


参考:

  1. Simple Single Sign-On With Spring Security OAuth2: https://www.baeldung.com/sso-spring-security-oauth2

最后,欢迎大家关注我的公众号:猿生物语,搜索关键词或扫码:
在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,142人围观)

还没有评论,来说两句吧...

相关阅读

    相关 CAS登录(SSO)教程

    注:本文为网上多个帖子翻阅结合亲自实验得来,并非纯原创 一、教程说明 前言 教程目的:从头到尾细细道来单点登录服务器及客户端应用的每个步骤 单点登

    相关 XXL-SSO 实现SSO登录

    一、 概述: 本文旨在使用XXL-SSO开源架构 实现单点登录系统。 XXL-SSO 是一个分布式单点登录框架、只需要登录一次就可以访问所有相互信任的应用系统。 拥有”