SSO(Single Sign On)也称为单点登录,多应用于多系统共存的场景中。

就好比现在的分布式系统应用,如果每一个子系统都需要去重复实现认证授权的逻辑,不仅对开发来说是一种负担,对用户而言更是一种非常差的体验。

单点登录就解决了这一痛点,而实现单点登录的方式也是层出不穷。

从一开始的使用浏览器 cookie 作为用户信息的存储媒介,到现在广为使用的页面重定向方式实现用户信息的安全传递。

而本此教程也是基于页面重定向的方式来实现 SSO 功能。

🚀 源码地址:https://github.com/NekoChips/SpringDemo/12.springboot-oauth2-sso

1. 搭建框架

之前简单地了解了 SpringSecurity 的使用,以及 SpringSecurity 集成 OAuth2 实现应用授权。具体可参照 Spring Security 使用指南基于 SpringSecurity OAuth2 实现应用授权

为了贴合实际应用场景,首先创建一个父级模块 springboot-oauth2-sso

<?xml version="1.0" encoding="UTF-8"?>
<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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>springboot-oauth2-sso</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.9.RELEASE</version>
        <relativePath/>
    </parent>

    <name>springboot-oauth2-sso</name>
    <description>Demo project for oauth2 sso of springboot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR3</spring-cloud.version>
        <mybaitplus.version>3.3.0</mybaitplus.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>

        <!-- 数据库连接依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- 引入 mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybaitplus.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-autoconfigure</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- 日志 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

在父级模块下创建一个子模块系统认证服务模块 springboot-oauth2-auth-server

<?xml version="1.0" encoding="UTF-8"?>
<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">
    <parent>
        <artifactId>springboot-oauth2-sso</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springboot-oauth2-auth-server</artifactId>

    <name>springboot-oauth2-auth-server</name>
    <description>Demo project for SSO auth server</description>

</project>

在父级模块下创建两个资源服务模块 springboot-oauth2-resource-userspringboot-oauth2-resource-order

springboot-oauth2-resource-user

<?xml version="1.0" encoding="UTF-8"?>
<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">
    <parent>
        <artifactId>springboot-oauth2-sso</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springboot-oauth2-resource-user</artifactId>

    <name>springboot-oauth2-resource-user</name>
    <description>Demo project for SSO user resource</description>

</project>

springboot-oauth2-resource-order

<?xml version="1.0" encoding="UTF-8"?>
<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">
    <parent>
        <artifactId>springboot-oauth2-sso</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springboot-oauth2-auth-server</artifactId>

    <name>oauth2-auth-server</name>
    <description>Demo project for SSO auth server</description>

</project>

搭建完毕后项目目录结构如下:

image.png

父级模块 pom 文件中会自动添加引用

    <modules>
        <module>springboot-oauth2-auth-server</module>
        <module>springboot-oauth2-resource-user</module>
        <module>springboot-oauth2-resource-order</module>
    </modules>

2. 认证服务器

基于 SpringSecurity OAuth2 实现应用授权一文中,我们使用 SpringSecurity 实现了用户的认证授权功能。里面的部分功能可以沿用,详细说明这里不做赘述。

springboot-oauth2-auth-server 中添加认证服务器

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userService;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired
    private TokenStore jwtTokenStore;

    @Autowired
    private JwtTokenEnhancer jwtTokenEnhancer;

    // 密码加密、校验器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public ClientDetailsService jdbcClientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(jdbcClientDetailsService());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> enhancers = new ArrayList<>();
        enhancers.add(jwtTokenEnhancer);
        enhancers.add(jwtAccessTokenConverter);
        enhancerChain.setTokenEnhancers(enhancers);

        endpoints
                // 配置 password 方式获取 token 令牌,则需要添加认证管理器以及用户查询接口
                .authenticationManager(authenticationManager)
                .userDetailsService(userService)
                // 使用 jwtToken 方式管理 token
                .tokenStore(jwtTokenStore)
                .accessTokenConverter(jwtAccessTokenConverter)
                .tokenEnhancer(enhancerChain);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        // 身份认证后才允许获取密钥
        security.tokenKeyAccess("isAuthenticated()");
    }
}

配置 SecurityConfig 使用 SpringSecurity 提供的表单登录

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated();
    }
}

application.ym l 文件中添加 Servlet 监听路径

server:
  address: localhost
  port: 8080
  servlet:
    context-path: /auth-server

至此,认证服务器配置告一段落。

3. 资源服务器

在实际开发过程中,我们无需关注子系统认证授权的过程,只需要进行简单的配置即可。

👀 下面只针对用户模块进行配置,订单模块可以此作为参照进行相应修改。

3.1 配置启动类

在子系统的启动类上添加 SpringSecurity OAuth2 提供的 @EnableOAuth2Sso 注解,用于开启 SSO 支持。

@SpringBootApplication
@EnableOAuth2Sso
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}

3.2 修改配置文件

在子系统的 application.yml 文件上添加如下配置:

server:
  address: localhost
  port: 9091
  servlet:
    context-path: /user-service

security:
  oauth2:
    client:
      client-id: app-user
      client-secret: user_123456
      user-authorization-uri: http://localhost:8080/auth-server/oauth/authorize
      access-token-uri: http://localhost:8080/auth-server/oauth/token
    resource:
      token-info-uri: http://localhost:8080/auth-server/oauth/check_token
      jwt:
        key-uri: http://localhost:8080/auth-server/oauth/token_key

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: demo
    password: demo

3.3 添加资源接口

先创建 Controller 接口,目前不添加权限控制

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

    /**
     * 获取 用户信息
     * @param principal 用户信息
     * @return 用户信息
     */
    @RequestMapping("principal")
    public Principal user(Principal principal) {
        return principal;
    }
}

3.4 测试效果

👀 先将客户端的信息添加至数据库,否则用户模块无法启动成功。添加接口源码中已有提供,也可以自行向数据库中添加。

先启动 认证服务 模块,再启动 用户模块,访问 http://localhost:9091/user-service/user/principal

页面重定向至认证服务器模块的登录页面

image.png

登录验证后,页面 OAuth Error

image.png

提示我们需要设置重定向地址,地址栏中显示 redirect_uri=localhost:9091/user-service/login,我们将其更新至数据库。

重新访问 http://localhost:9091/user-service/user/principal

image.png

而后访问 Order 模块 的接口 http://localhost:9092/order-service/order/info

image.png

无需登录认证,直接可以访问。

至此,单点登录的基本功能已经完成。

4. 权限配置

开启权限配置前面在 Spring Security 使用指南 中讲过,这里就不做赘述了。

在需要开启权限认证的资源服务器中,在启动类上开启注解。

@SpringBootApplication
@EnableOAuth2Sso
@Order(101)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}

有权限和没有权限的验证这里就不做演示了。

5. 总结

自从了解 SpringSecurity OAuth2 以来,越发觉得它使用起来也是非常的便捷,只需短短的几行配置就代替了之前需要编写的很多逻辑代码。

当然目前还有很多细节性的问题没有考虑到,源码只是看了目前使用到的一部分。

一直以来项目中引用的 OAuth2 依赖均是使用的 SpringCloud 的集成依赖,这也是为后续的 SpringCloud 环境集成打下基础。后续将会使用 OAuth2 作为 SpringCloud 网关,用于控制对 SpringCloud 中资源的访问。

🚀 源码地址:https://github.com/NekoChips/SpringDemo/12.springboot-oauth2-sso

🚀 相关链接:

  1. SpringSecurity 使用教程:Spring Security 使用指南
  2. SpringSecurity OAuth2 基础:基于 SpringSecurity OAuth2 实现应用授权
  3. SpringSecurity OAuth2 官方文档:OAuth 2 Developers Guide

关于作者:NekoChips
本文地址:https://chenyangjie.com.cn/articles/2020/03/15/1584254982408.html
版权声明:本篇所有文章仅用于学习和技术交流,本作品采用 BY-NC-SA 4.0 许可协议,如需转载请注明出处!
许可协议:知识共享许可协议