SpringBoot跨系统单点登陆的实现方法

什么是单点登陆

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一退出(single sign-off)就是指,只需要单一的退出动作,就可以结束对于多个系统的访问权限。

单点登陆带来的好处

  • 降低访问第三方网站的风险(不存储用户密码,或在外部管理)
  • 减少因不同的用户名和密码组合而带来的密码疲劳
  • 减少为相同的身份重新输入密码所花费的时间
  • 因减少与密码相关的调用IT服务台的次数而降低IT成本

单点登陆技术

现在很多语言都拥有自己的单点登陆实现方案,本次案例中我们用SpringBoot Oauh2来实现跨系统的单点登陆

单点登陆 流程

你的项目可能有很多个模块,如订单管理、商户管理、会员管理、财务管理,这些都是需要登陆后才能访问,当我只要登陆一次,其它的系统都能访问。

ps这张图网上找的,也是最清晰描述单点登陆的流程,如上图就是最基本的单点登陆流程。

oauth2 的四种模式:

  • 密码模式(resource owner password credentials)
  • 授权码模式(authorization code)
  • 简化模式(implicit)
  • 客户端模式(client credentials)

我们一般都用授权码模式 这个模式用的人也最多。

这几种模式如果想要了解的更清楚可以看阮一峰老师的oauth2
阮一峰老师的oauth2精讲

单点登陆准备工作

首先我们创建一个叫spring_sso_parent 普通的maven工程 作为整个项目的父工程,创建好后,删除src目录,并且修改pom.xml的依赖

spring_sso_parent 父工程的依赖如下:

<?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>

 <!-- 父工程 -->

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.6.RELEASE</version>
    <relativePath/>
  </parent>
  <groupId>cn.com.scitc</groupId>
  <artifactId>spring_sso_parent</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

<!-- 通用配置 -->
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>

     <!-- spring oauth2 版本 -->

    <oauth.version>2.3.6.RELEASE</oauth.version>
    <!-- Spring Security OAuth2 AutoConfigure 版本 -->
    <oauth-auto.version>2.1.6.RELEASE</oauth-auto.version>
  </properties>
</project>

开始编写单点登陆

我们在spring_sso_parent 父工程中 添加一个子模块叫oauth_server的SpringBoot工程,
依赖如下

<?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>
  <parent>
    <groupId>cn.com.scitc</groupId>
    <artifactId>spring_sso_parent</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <groupId>cn.com.scitc</groupId>
  <artifactId>oauth_server</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>oauth_server</name>
  <packaging>war</packaging>
  <description>this is oauth2 server</description>

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

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

    <dependency>
      <groupId>org.springframework.security.oauth</groupId>
      <artifactId>spring-security-oauth2</artifactId>
      <version>${oauth.version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

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

</project>

需要注意的是这里的SpringBoot 版本使用的是父模块的版本

<parent>
    <groupId>cn.com.scitc</groupId>
    <artifactId>spring_sso_parent</artifactId>
    <version>1.0-SNAPSHOT</version>
</parent>

我们在oauth_server 中创建一个config的包,并且创建一个WebSecurityConfig的类

@Configuration
@Order(1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.requestMatchers()
      .antMatchers("/login")
      .antMatchers("/oauth/authorize")
      .and()
      .authorizeRequests()
      .anyRequest().authenticated()
      .and()
      .formLogin().loginPage("/login").permitAll()
      .and().csrf().disable();
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //使用内存模拟数据库查询的用户
    auth.inMemoryAuthentication() //内存认证
      .withUser("admin")//admin 内存认证用户名
      .password(passwordEncoder().encode("123456"))//被加密的123456密码
      .roles("ADMIN");//ROLE_ADMIN的角色
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }
}

这个类使用了两个注解,@Configuration 让这个类成为了一个配置类, @Order(1) 这个注解是优先级,使用优先级来加载。

 http.requestMatchers()
      .antMatchers("/login")
      .antMatchers("/oauth/authorize")

http.requestMatchers() 这个方法下配置的就是security 接收以什么样的请求,我们这里只接受/login和/oauth/authorize的请求 。

 .authorizeRequests()
 .anyRequest().authenticated()

这两句配置的意思是除了以上请求所有的请求都需要身份认证才能访问。

.formLogin().loginPage("/login").permitAll()
.and().csrf().disable();

这几个配置的意思是采用form表单登陆默认登陆页面是/login,任何人都能访问,关闭csrf的保护。

@Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //使用内存模拟数据库查询的用户
    auth.inMemoryAuthentication()
      .withUser("admin")
      .password(passwordEncoder().encode("123456"))
      .roles("ADMIN");
  }

这里采用的是AuthenticationManagerBuilder 允许内存验证,这里我添加了一个用户名为admin 密码是 123456,角色是ADMIN的 一个用户 来模拟数据库查询的用户信息。

 @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

PasswordEncoder 是Spring 官方提供的一个md5 密码加密器,一般用于密码的加密。

这个就是WebSecurityConfig的配置

下面我们在config中继续创建一个叫OauthServerConfig的类

@Configuration
@EnableAuthorizationServer
public class OauthServerConfig extends AuthorizationServerConfigurerAdapter {

  @Autowired
  private PasswordEncoder passwordEncoder;

  @Override
  public void configure(final AuthorizationServerSecurityConfigurer security) throws Exception {
    security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
  }

  @Override
  public void configure(final ClientDetailsServiceConfigurer clients)
  throws Exception {
    clients.inMemory()
        .withClient("handleCilentId")//客户端id
        .secret(passwordEncoder.encode("secret"))//客户端密钥
        .authorizedGrantTypes("authorization_code")//授权码模式
        .scopes("user_info") //授权范围
        .autoApprove(true)//开启自动授权
        .redirectUris("http://localhost:8882/login") //认证成功重定向
        .accessTokenValiditySeconds(10);//设置超时时间
  }
}

这个类上也使用了两个注解,@Configuration 这个注解成为Spring的一个配置类,@EnableAuthorizationServer 注解是开启授权服务器认证

这个类继承了AuthorizationServerConfigurerAdapter 这个类提供了授权服务器策略。

这里我们实现了两个configure 认证策略方法,分别是AuthorizationServerSecurityConfigurer 和 ClientDetailsServiceConfigurer,
而AuthorizationServerSecurityConfigurer提供了十几个配置的方法,这里我们不会多去深入
其中 tokenKeyAccess意思是:oauth2授权服务器会提供一个/oauth/token_key的url来供资源服务器获取公钥,这个方法就是配置获取公钥的权限范围,它使用的是SpEL表达式且默认不开启, 这里我们使用的是permitAll(),让本身的oauth的访问不需要授权

checkTokenAccess意思是:授权服务器提供一个/oauth/check_token的url来供资源服务器解码令牌,该方法就是配置权限范围,同样使用的是SpEL表达式且默认不开启,我们这里设置的是 isAuthenticated(),检查access_token需要进行授权

当客户端向认证服务器认证的时候,我们需要判断这个客户端是否通过了认证那么就要使用ClientDetailsServiceConfigurer 它提供了三种认证方式

  • clients.withClientDetails() :使用数据库认证
  • clients.jdbc(): 传入一个dataSource 这里可以使用自定义的dataSource
  • clients.inMemory():内存认证 相当于将认证信息 写死

这样我们就将授权服务器配置好了。

下面我们创建一个controller的包
创建一个LoginController 登陆的控制器

@Controller
public class LoginController {
  @GetMapping("/login")
  public String loginPage() {
    return "login";
  }
}

这里返回的是一个login的 html 页面

login.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>login</title>
</head>
<body>
<h1>标准登陆</h1>
<form action="/auth/login" method="post">

 username: <input type="text" name="username"/> <br/>
 password: <input type="password" name="password"/> <br/>
 <button type="submit">登陆</button>

</form>
</body>
</html>

在创建一个UserInfoController 用于获取认证成功的用户信息

@RestController
public class UserInfoController {
  private Logger logger = LoggerFactory.getLogger(this.getClass());
  @RequestMapping("/user")
  public ResponseEntity<Object> getUser(Principal principal) {
    logger.info("principal:" + principal);
    return new ResponseEntity<Object>(principal, HttpStatus.OK);
  }
}

applicatin.yml 配置

server:
 port: 8880
 servlet:
  context-path: /auth

然后我们创建2个客户端分别是oauth_client1 和 oauth_client2
oauth_client1 的依赖如下:

<?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>
  <parent>
   <groupId>cn.com.scitc</groupId>
   <artifactId>spring_sso_parent</artifactId>
   <version>1.0-SNAPSHOT</version>
  </parent>
  <groupId>cn.com.scitc</groupId>
  <artifactId>oauth_clinet1</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>oauth_clinet1</name>
  <description>this is client1</description>

  <properties>
    <java.version>1.8</java.version>
  </properties>

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

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

    <dependency>
      <groupId>org.springframework.security.oauth.boot</groupId>
      <artifactId>spring-security-oauth2-autoconfigure</artifactId>
      <version>${oauth-auto.version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

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

</project>

同样创建一个config 包 并且创建一个 Oauth2ClientSeurityConfig这个类

@Configuration
@EnableOAuth2Sso
public class Oauth2ClientSeurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable() //关闭csrf保护
      .antMatcher("/**") //使用以任意开头的url
      .authorizeRequests() // 配置路径拦截,表明路径访问所对应的权限,角色,认证信息
      .antMatchers("/", "/login**") //控制不同的url接受不同权限的用户访问
      .permitAll()// 允许所有人访问
      .anyRequest()
      .authenticated(); //除了以上请求都需要身份认证
  }
}

这个类继承了 WebSecurityConfigurerAdapter 这个SpringSecurity的适配器,实现了HttpSecurity 的 configure 方法。 这个类也是两个注解 @Configuration 成为一个配置类,
@EnableOAuth2Sso 启用Oauth2的单点登陆。

我们再创建一个controller 包 ,并且创建一个 InfoController

@Controller
public class InfoController {
  @GetMapping("/getUser")
  public ResponseEntity<Object> userPage(Principal principal) {
    //客户端认证成功后返回这个用户信息
    return new ResponseEntity<Object>(principal, HttpStatus.OK);
  }

  @GetMapping("/")
  public String indexPage() {
    return "index";
  }
}

index.html 页面

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>index</title>
</head>
<body>
<h1>请登录授权</h1>
<a href="/getUser" rel="external nofollow" >login</a>
</body>
</html>

application.yml

auth-server: http://localhost:8880/auth
server:
 port: 8881
 servlet:
  context-path: /

security:
 basic:
  enabled: false

 oauth2:
  client:
   clientId: handleCilentId
   clientSecret: secret
   accessTokenUri: ${auth-server}/oauth/token
   userAuthorizationUri: ${auth-server}/oauth/authorize
  resource:
   userInfoUri: ${auth-server}/user
spring:
 thymeleaf:
  cache: false

auth-server:是目标认证服务器
clientId: 目标认证服务器设置的客户端id
clientSecret: 目标认证服务器设置的密码
accessTokenUri:从目标认证服务器获取令牌token
userAuthorizationUri:从目标认证服务器请求授权默认url是/oauth/authorize
userInfoUri: 从目标认证服务器上将认证信息Principal通过形参绑定的方法通过URL的方式获取用户信息

oauth_client2配置和 oauth_client1是一样的

我们启动 认证服务器oauth_server 和 两个客户端 oauth_client1 和 oauth_client2
chrome 浏览器访问 localhost:8881

当我们点击login的时候会跳转到认证服务器进行登陆授权

授权成功后 返回了 这个用户的所有的信息

我们再去访问localhost:8082

当我点击登陆的时候 ,并没有出现登陆授权,直接拿到了用户信息

注意这里我们不管是访问客户端1还是客户端2 ,还是n多个客户端,只要有一个授权成功 那么访问其它的客户端就不需要登陆 就能访问相关的rest服务了。

总结

oauth2 实现的单点登陆 并不是很复杂,归根结底,Spring帮我们做了太多的底层实现,让我们实现起来非常的简单 实现几个接口就可以搞定授权服务器的配置,客户端的配置也非常的简单。现在我们流行看到了如百度,我登陆了百度,那么就可以直接访问百度贴吧,百度糯米,等。单点登陆运用已经使用的非常的广泛。所以我认为掌握单点登陆是十分有必要的。

源码地址

github

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • spring boot整合CAS Client实现单点登陆验证的示例

    本文介绍了spring boot整合CAS Client实现单点登陆验证的示例,分享给大家,也给自己留个笔记,具体如下: 单点登录( Single Sign-On , 简称 SSO )是目前比较流行的服务于企业业务整合的解决方案之一, SSO 使得在多个应用系统中,用户只需要 登录一次 就可以访问所有相互信任的应用系统. CAS Client 负责处理对客户端受保护资源的访问请求,需要对请求方进行身份认证时,重定向到 CAS Server 进行认证.(原则上,客户端应用不再接受任何的用户名密码等

  • SpringBoot跨系统单点登陆的实现方法

    什么是单点登陆 单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性.当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录.这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中.相同的,单一退出(single sign-off)就是指,只需要单一的退出动作,就可以结束对于多个系统的访问权限. 单点登陆带来的好处 降低访问

  • Springboot Vue实现单点登陆功能示例详解

    目录 正文 简单上个图 先分析下登陆要做啥 怎么落实? 上代码 接口: token生成部分 刷新token 验证token 正文 登陆是系统最基础的功能之一.这么长时间了,一直在写业务,这个基础功能反而没怎么好好研究,都忘差不多了.今天没事儿就来撸一下. 以目前在接触和学习的一个开源系统为例,来分析一下登陆该怎么做.代码的话我就直接CV了. 简单上个图 (有水印.因为穷所以没开会员) 先分析下登陆要做啥 首先,搞清楚要做什么. 登陆了,系统就知道这是谁,他有什么权限,可以给他开放些什么业务功能,

  • SpringBoot集成JWT实现登陆验证的方法详解

    1:首先,我们需要在项目中导入两个依赖: <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.3</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifa

  • .NET单点登陆的实现方法及思路

    系统的基本架构 我们假设一个系统System包含Service客户服务中心.Shop网上购物中心和Office网上办公中心三个独立的网站. Service管理客户的资料,登录和注销过程.不论客户访问System的任何一个页面,系统都会转到登录界面,在用户登录后,系统会自动转会到客户上 次请求的页面.并且用户此后可以在System中无缝切换.不需要再次进行登录.即在System中实现单点登录SSO(Single Sign-On). 我们知道,用户的即时状态通常是使用Application.Sess

  • SpringBoot跨域Jsonp和Cors的方法

    有一次我的项目中采用了前后端分离的模式,引起了跨域问题,本文将介绍我所采用的跨域解决方法. 首先要了解产生跨域的本质,也就是同源策略的限制,源是指域名.端口号.协议,有一者不相同将被浏览器拒绝接受响应信息,(请求可以发送出去,但是浏览器不接受响应). 解决方法: 1. jsonp jsonp的原理的 src="" 属性不受同源策略的限制,动态创建一个callback回调函数,服务器调用回调函数把数据放进去,具体的细节打算以后做一个专门讲解. 这里给一个模版: $.ajax({ type

  • Springboot处理CORS跨域请求的三种方法

    前言 Springboot跨域问题,是当前主流web开发人员都绕不开的难题.但我们首先要明确以下几点 跨域只存在于浏览器端,不存在于安卓/ios/Node.js/python/ java等其它环境 跨域请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了. 之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议.域名.端口号都完全一致. 浏览器出于安全的考虑,使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略,否则就是跨域的H

  • Springboot 跨域配置无效及接口访问报错的解决方法

    跨域配置如下,Springboot 版本为 2.4.1 ///跨域访问配置 @Configuration public class CorsConfig { private CorsConfiguration buildConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowCredentials(true); //sessionid 多次访问一致 co

  • SpringBoot跨域问题的解决方法实例

    谈到跨域问题,首先我们要认识一下浏览器的同源策略 百度百科对同源策略的解释 当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面 当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的, 即检查是否同源,只有和百度同源的脚本才会被执行. [1] 如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问. 同源策略是浏览器的行为,是为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应

  • springboot集成shiro自定义登陆过滤器方法

    目录 前言 自定义UsernamePasswordAuthenticationFilter 覆盖默认的FormAuthenticationFilter 完整UsernamePasswordAuthenticationFilter代码 前言 在上一篇博客springboot集成shiro权限管理简单实现中,用户在登录的过程中,有以下几个问题: 用户在没有登陆的情况下,访问需要权限的接口,服务器自动跳转到登陆页面,前端无法控制: 用户在登录成功后,服务器自动跳转到成功页,前端无法控制: 用户在登录失

  • springboot跨域如何设置SameSite的实现

    前言 今天记录一个前段时间遇到的一个小问题的解决方法, 跨域!!! 相信跨域这个问题, 做开发的或多或少都遇到过, 而且已经有很多博主已经分享了相关的内容, 这次我用他们的方式都没有解决到, 所以记录一下. 问题 我们公司有个系统的域名跟主系统的域名不一致, 但是我们又需要把所有的系统都集成在一个框架下面, 使用的是iframe技术来实现. 使用单点登录来做所有系统的登录. 这样的设计就导致我们访问域名不同的系统的时候, 会有跨域的问题. 通常的解决方式这样, 在springboot里面设置跨域

随机推荐