单点登录(SingleSignOn简称SSO)系统实现单点功能

一、单点登录SSO介绍

目前,每个企业或平台都有多个系统。由于历史原因,每个系统都是从不同的厂家采购的,所以系统是相互独立的,有自己的用户认证和认证系统。用户登录系统时,需要记住各个系统的用户名和密码,同时管理员还需要为同一个用户设置多个系统登录帐号,这显然给用户带来了不便系统的。我们期望的是,如果有多个系统,你只需要登录一次就可以访问多个系统,并且只需要在其中一个系统上注销并登录,那么所有系统都会被注销,而无需重复操作,这是单点登录(Single Sign On (SSO) 系统实现的功能。

单点登录是系统功能的定义,实现单点登录功能有两种开源流行的方法:CAS和OAuth2。过去,我们用的最多的是 CAS。现在随着 SpringCloud 的普及,越来越多的人选择使用它。Spring Security 提供的 OAuth2 认证授权服务器实现了单点登录功能。

OAuth2 是一种授权协议标准。任何人都可以基于此标准开发 Oauth2 授权服务器。现在百度开放平台、腾讯开放平台等大部分开放平台都是基于OAuth2协议实现的。OAuth2.0定义有四种授权类型,最新版本的OAuth2.1协议定义了七种授权类型,其中两种由于安全问题不再推荐:

[OAuth2.1 推荐的五种授权类型] [OAuth2.1 中不推荐/禁止使用的两种授权类型] [Spring Security 对 OAuth2 协议的支持]:

从SpringSecurity官网可以看出,通过对OAuth2的长期支持以及对实际业务场景的考虑,大部分系统都不需要授权服务器。因此,Spring 官方不再推荐使用 spring-security-oauth2。将security-oauth2中的OAuth2登录、客户端、资源服务器等功能提取出来,集成到Spring Security中,并创建一个单独的spring-authorization-server项目来实现授权服务器功能。

目前我们最了解的是Spring Security OAuth对OAuth2协议的实现和支持。这里我们需要将 Spring Security OAuth 和 Spring Security 区分为两个项目。过去,OAth2 相关的功能都是在 Spring Security OAuth 项目中实现的,但是自从 SpringSecurity5.以 @>X 开始,SpringSecurity 项目开始逐渐增加 Spring Security OAuth 中的功能。从SpringSecurity5.2开始,增加了OAuth2.0登录、客户端、资源服务器功能。不过授权服务器的功能并不是要集成到SpringSecurity项目中,而是新建一个spring-authorization-server项目作为单独的授权服务器:详细介绍。spring-security 实现了 OAuth2.1 协议,

Spring 未来的计划是将 Spring Security OAuth 中当前的所有功能构建到 Spring Security 5.x 中。在 Spring Security 实现与 Spring Security OAuth 的功能对等后,他们将继续支持错误和安全修复至少一年。

【GitEgg框架单点登录实施方案】:

由于spring-authorization-server 0.2.3最新发布版本,部分功能还在修复完善中,不足以应用于实际生产环境。因此,我们目前使用 spring-security -oauth2 作为授权服务器。spring-authorization-server后续稳定版发布后,会进行迁移升级。

【spring-security-oauth2默认实现的授权类型】:

在GitEgg微服务框架中cas登录成功之后跳转的路径cas登录成功之后跳转的路径,gitegg-oauth引入了spring-security-oauth2,在代码中使用了Oauth2的密码授权和刷新token授权,并自定义扩展了【短信验证码授权类型】和【图形认证】密码授权],实际上是密码授权的扩展授权类型。

目前,基本上所有的SpringCloud微服务授权方式都使用OAuth2密码授权方式来获取token。你可能会有疑问,为什么上面最新的 Oauth2 协议不推荐甚至禁止使用密码授权类型,而我们的 GitEgg 框架系统的系统管理接口也使用密码授权方式来获取令牌呢?由于不推荐密码授权类型,第三方客户端会收集用户名和密码,存在安全隐患。在我们的例子中,我们的客户端是我们自己的系统管理界面,而不是第三方客户端。所有用户名和密码都是我们自己系统的用户名和密码。只要系统安全得到很好的保护,我们就可以最大限度的避免用户名和密码泄露给第三方的风险。

在使用spring-security-oauth2实现单点登录之前,我们首先要了解单点登录SSO和OAuth的区别和联系2、spring-security-oauth2:二、SpringSecurity单点登录-关于服务端和客户端实现流程分析单点登录业务流程时序图:

图片[1]-单点登录(SingleSignOn简称SSO)系统实现单点功能-唐朝资源网

系统(单点登录客户端)首次访问受保护资源触发单点登录流程描述三、使用【Authorization Code Authorization]和【Refresh Token Authorization】实现SSO服务器1、自定义 SSO 服务器页面

当我们的 gitegg-oauth 被用作授权服务器时,我们想要自定义我们自己的登录页面和其他信息。接下来,我们自定义登录、主页、错误提示页面和密码恢复页面。其他需要的页面可以自己定义,比如授权确认页面。我们这里的业务不需要用户二次确认,所以这里没有定制这个页面。

        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        

    /**
     * 单点登录-登录页
     * @return
     */
    @GetMapping("/login") public String login() {
        return "login";
    }
    /**
     * 单点登录-首页:当直接访问单点登录系统成功后进入的页面。从客户端系统进入的,直接返回到客户端页面
     * @return
     */
    @GetMapping("/index") public String index() {
        return "index";
    }
    /**
     * 单点登录-错误页
     * @return
     */
    @GetMapping("/error") public String error() {
        return "error";
    }
    /**
     * 单点登录-找回密码页
     * @return
     */
    @GetMapping("/find/pwd") public String findPwd() {
        return "findpwd";
    }




    
    
    
    
    统一身份认证平台
    
    
    
    
    
    
    


    <div
        <div
                <div
                    
<div <span 图片[2]-单点登录(SingleSignOn简称SSO)系统实现单点功能-唐朝资源网 <span <span <p GitEgg Cloud 统一身份认证平台

<div
    <li
  • 验证码登录
<div <i <path d="M858.5 763.6c-18.9-44.8-46.1-85-80.6-119.5-34.5-34.5-74.7-61.6-119.5-80.6-0.4-0.2-0.8-0.3-1.2-0.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-0.4 0.2-0.8 0.3-1.2 0.5-44.8 18.9-85 46-119.5 80.6-34.5 34.5-61.6 74.7-80.6 119.5C146.9 807.5 137 854 136 901.8c-0.1 4.5 3.5 8.2 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c0.1 4.4 3.6 7.8 8 7.8h60c4.5 0 8.1-3.7 8-8.2-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z" p-
<div <i <path d="M832 464h-68V240c0-70.7-57.3-128-128-128H388c-70.7 0-128 57.3-128 128v224h-68c-17.7 0-32 14.3-32 32v384c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V496c0-17.7-14.3-32-32-32zM332 240c0-30.9 25.1-56 56-56h248c30.9 0 56 25.1 56 56v224H332V240z m460 600H232V536h560v304z" p- d="M484 701v53c0 4.4 3.6 8 8 8h40c4.4 0 8-3.6 8-8v-53c12.1-8.7 20-22.9 20-39 0-26.5-21.5-48-48-48s-48 21.5-48 48c0 16.1 7.9 30.3 20 39z" p-
<button type="submit" <div
<div <i <path d="M744 62H280c-35.3 0-64 28.7-64 64v768c0 35.3 28.7 64 64 64h464c35.3 0 64-28.7 64-64V126c0-35.3-28.7-64-64-64z m-8 824H288V134h448v752z" p- d="M512 784m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" p-
<div <i <path d="M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32z m-40 110.8V792H136V270.8l-27.6-21.5 39.3-50.5 42.8 33.3h643.1l42.8-33.3 39.3 50.5-27.7 21.5z" p- d="M833.6 232L512 482 190.4 232l-42.8-33.3-39.3 50.5 27.6 21.5 341.6 265.6c20.2 15.7 48.5 15.7 68.7 0L888 270.8l27.6-21.5-39.3-50.5-42.7 33.2z" p-
<div 获取验证码
<button type="submit" <div Copyrights © 2021 GitEgg All Rights Reserved.

var countdown=60;
jQuery(function ($) {
    countdown = 60;
    $('.account-form').bootstrapValidator({
        message: '输入错误',
        feedbackIcons: {
            valid: 'glyphicon glyphicon-ok',
            invalid: 'glyphicon glyphicon-remove',
            validating: 'glyphicon glyphicon-refresh'
        },
        fields: {
            username: {
                container: '.input-account-wrapper',
                message: '输入错误',
                validators: {
                    notEmpty: {
                        message: '用户账号不能为空'
                    },
                    stringLength: {
                        min: 2,
                        max: 32,

图片[3]-单点登录(SingleSignOn简称SSO)系统实现单点功能-唐朝资源网

message: '账号长度范围2-32个字符。' }, regexp: { regexp: /^[a-zA-Z0-9_.]+$/, message: '用户名只能由字母、数字、点和下划线组成' } } }, password: { container: '.input-psw-wrapper', validators: { notEmpty: { message: '密码不能为空' }, stringLength: { min: 5, max: 32, message: '密码长度范围6-32个字符。' } } } } }); $('.mobile-form').bootstrapValidator({ message: '输入错误', feedbackIcons: { valid: 'glyphicon glyphicon-ok', invalid: 'glyphicon glyphicon-remove', validating: 'glyphicon glyphicon-refresh' }, fields: { phone: { message: '输入错误', container: '.input-phone-wrapper', validators: { notEmpty: { message: '手机号不能为空' }, regexp: { regexp: /^1d{10}$/, message: '手机号格式错误' } } }, code: { container: '.input-sms-wrapper', validators: { notEmpty: { message: '验证码不能为空' }, stringLength: { min: 6, max: 6, message: '验证码长度为6位。' } } } } }); var options={ beforeSerialize: beforeFormSerialize, success: formSuccess,//提交成功后执行的回掉函数 error: formError,//提交失败后执行的回掉函数 headers : {"TenantId" : 0}, clearForm: true,//提交成功后是否清空表单中的字段值 restForm: true,//提交成功后是否充值表单中的字段值,即恢复到页面加载是的状态 timeout: 6000//设置请求时间,超过时间后,自动退出请求,单位(毫秒) } var mobileOptions={ success: mobileFormSuccess,//提交成功后执行的回掉函数 error: mobileFormError,//提交失败后执行的回掉函数 headers : {"TenantId" : 0}, clearForm: true,//提交成功后是否清空表单中的字段值 restForm: true,//提交成功后是否充值表单中的字段值,即恢复到页面加载是的状态 timeout: 6000//设置请求时间,超过时间后,自动退出请求,单位(毫秒) } function beforeFormSerialize(){ $("#account-err").html(""); $("#username").val($.trim($("#username").val())); $("#password").val($.md5($.trim($("#password").val()))); } function formSuccess(response){ $(".account-form").data('bootstrapValidator').resetForm(); if (response.success) { window.location.href = response.targetUrl; } else { $("#account-err").html(response.message); } } function formError(response){ $("#account-err").html(response);

图片[4]-单点登录(SingleSignOn简称SSO)系统实现单点功能-唐朝资源网

} function mobileFormSuccess(response){ $(".mobile-form").data('bootstrapValidator').resetForm(); if (response.success) { window.location.href = response.targetUrl; } else { $("#mobile-err").html(response.message); } } function mobileFormError(response){ $("#mobile-err").html(response); } $(".account-form").ajaxForm(options); $(".mobile-form").ajaxForm(mobileOptions); $(".nav-left a").click(function(e){ $(".account-login").show(); $(".mobile-login").hide(); }); $(".nav-right a").click(function(e){ $(".account-login").hide(); $(".mobile-login").show(); }); $("#forget").click(function(e){ window.location.href = "/find/pwd"; }); $('.title-list li').click(function(){ var liindex = $('.title-list li').index(this); $(this).addClass('on').siblings().removeClass('on'); $('.login_right div.login-form-container').eq(liindex).fadeIn(150).siblings('div.login-form-container').hide(); var liWidth = $('.title-list li').width(); if (liindex == 0) { $('.login_right .title-list p').css("transform","translate3d(0px, 0px, 0px)"); } else { $('.login_right .title-list p').css("transform","translate3d("+liWidth+"px, 0px, 0px)"); } }); }); function sendCode(){ $(".mobile-form").data('bootstrapValidator').validateField('phone'); if(!$(".mobile-form").data('bootstrapValidator').isValidField("phone")) { return; } if(countdown != 60) { return; } sendmsg(); var phone = $.trim($("#phone").val()); var tenant $.ajax({ //请求方式 type : "POST", //请求的媒体类型 contentType: "application/x-www-form-urlencoded;charset=UTF-8", dataType: 'json', //请求地址 url : "/code/sms/login", //数据,json字符串 data : { tenantId: tenantId, phoneNumber: phone, code: "aliValidateLogin" }, //请求成功 success : function(result) { $("#smsId").val(result.data); }, //请求失败,包含具体的错误信息 error : function(e){ console.log(e); } }); }; function sendmsg(){ if(countdown==0){ $("#sendBtn").css("color","#181818"); $("#sendBtn").html("获取验证码"); countdown=60; return false; } else{ $("#sendBtn").css("color","#74777b"); $("#sendBtn").html("重新发送("+countdown+")"); countdown--; } setTimeout(function(){ sendmsg(); },1000); }

2、授权服务器配置

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/assets/**", "/css/**", "/images/**");
    }

# 以下配置为新增
  whiteUrls:
    - "/gitegg-oauth/oauth/login"
    - "/gitegg-oauth/oauth/find/pwd"
    - "/gitegg-oauth/oauth/error"
  authUrls:
    - "/gitegg-oauth/oauth/index"
  whiteUrls:
    - "/*/v2/api-docs"
    - "/gitegg-oauth/oauth/public_key"
    - "/gitegg-oauth/oauth/token_key"
    - "/gitegg-oauth/find/pwd"
    - "/gitegg-oauth/code/sms/login"
    - "/gitegg-oauth/change/password"
    - "/gitegg-oauth/error"
    - "/gitegg-oauth/oauth/sms/captcha/send"
  # 新增OAuth2认证接口,此处网关放行,由认证中心进行认证
  tokenUrls:
    - "/gitegg-oauth/oauth/token"

package com.gitegg.oauth.filter;
import cn.hutool.core.bean.BeanUtil;
import com.gitegg.oauth.token.PhoneAuthenticationToken;
import com.gitegg.platform.base.constant.AuthConstant;
import com.gitegg.platform.base.domain.GitEggUser;
import com.gitegg.platform.base.result.Result;
import com.gitegg.service.system.client.feign.IUserFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * 自定义登陆
 * @author GitEgg
 */
public class GitEggLoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    public static final String SPRING_SECURITY_RESTFUL_TYPE_PHONE = "phone";
    public static final String SPRING_SECURITY_RESTFUL_TYPE_QR = "qr";
    public static final String SPRING_SECURITY_RESTFUL_TYPE_DEFAULT = "user";
    //  登陆类型:user:用户密码登陆;phone:手机验证码登陆;qr:二维码扫码登陆
    private static final String SPRING_SECURITY_RESTFUL_TYPE_KEY = "type";
    //  登陆终端:1:移动端登陆,包括微信公众号、小程序等;0:PC后台登陆
    private static final String SPRING_SECURITY_RESTFUL_MOBILE_KEY = "mobile";
    private static final String SPRING_SECURITY_RESTFUL_USERNAME_KEY = "username";
    private static final String SPRING_SECURITY_RESTFUL_PASSWORD_KEY = "password";
    private static final String SPRING_SECURITY_RESTFUL_PHONE_KEY = "phone";
    private static final String SPRING_SECURITY_RESTFUL_VERIFY_CODE_KEY = "code";
    private static final String SPRING_SECURITY_RESTFUL_QR_CODE_KEY = "qrCode";
    @Autowired
    private IUserFeign userFeign;
    private boolean postOnly = true;
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (postOnly && !"POST".equals(request.getMethod())) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }
        String type = obtainParameter(request, SPRING_SECURITY_RESTFUL_TYPE_KEY);
        String mobile = obtainParameter(request, SPRING_SECURITY_RESTFUL_MOBILE_KEY);

        AbstractAuthenticationToken authRequest;
        String principal;
        String credentials;
        // 手机验证码登陆
        if(SPRING_SECURITY_RESTFUL_TYPE_PHONE.equals(type)){
            principal = obtainParameter(request, SPRING_SECURITY_RESTFUL_PHONE_KEY);
            credentials = obtainParameter(request, SPRING_SECURITY_RESTFUL_VERIFY_CODE_KEY);
            principal = principal.trim();
            authRequest = new PhoneAuthenticationToken(principal, credentials);
        }
        // 账号密码登陆
        else {
            principal = obtainParameter(request, SPRING_SECURITY_RESTFUL_USERNAME_KEY);
            credentials = obtainParameter(request, SPRING_SECURITY_RESTFUL_PASSWORD_KEY);
            Result result = userFeign.queryUserByAccount(principal);
            if (null != result && result.isSuccess()) {
                GitEggUser gitEggUser = new GitEggUser();
                BeanUtil.copyProperties(result.getData(), gitEggUser, false);
                if (!StringUtils.isEmpty(gitEggUser.getAccount())) {
                    principal = gitEggUser.getAccount();
                    credentials = AuthConstant.BCRYPT + gitEggUser.getAccount() + credentials;
                }
            }
            authRequest = new UsernamePasswordAuthenticationToken(principal, credentials);
        }
        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }
    private void setDetails(HttpServletRequest request,
                            AbstractAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }
    private String obtainParameter(HttpServletRequest request, String parameter) {
        String result =  request.getParameter(parameter);
        return result == null ? "" : result;
    }
}

四、实现单点登录客户端

spring-security-oauth2 提供 OAuth2 授权服务器和单点登录客户端的实现。一般通过几行注释就可以实现单点登录功能。

1、新建单点登录客户端项目,引入oauth2客户端相关jar包

        
            org.springframework.boot
            spring-boot-starter-oauth2-client
        
        
            org.springframework.boot
            spring-boot-starter-security
        
        
            org.springframework.security.oauth.boot
            spring-security-oauth2-autoconfigure
        

2、新建WebSecurityConfig类并添加@EnableOAuth2Sso注解

@EnableOAuth2Sso
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .csrf().disable();
    }
}

3、配置SSO服务器相关信息

server:
  port: 8080
  servlet:
    context-path: /ssoclient1
security:
  oauth2:
    client:
      # 配置在授权服务器配置的客户端id和secret
      client-id: ssoclient
      client-secret: 123456
      # 获取token的url
      access-token-uri: http://127.0.0.1/gitegg-oauth/oauth/token
      # 授权服务器的授权地址
      user-authorization-uri: http://127.0.0.1/gitegg-oauth/oauth/authorize
    resource:
      jwt:
        # 获取公钥的地址,验证token需使用,系统启动时会初始化,不会每次验证都请求
        key-uri: http://127.0.0.1/gitegg-oauth/oauth/token_key

评论:

1、令牌返回格式在 GitEgg 框架中自定义。Spring Security为token获取的/oauth/token默认返回一个ResponseEntity,在使用自己的系统登录和单点登录时需要进行转换。

2、Gateway认证需要的公钥地址是gitegg-oauth/oauth/public_key,单点登录客户端需要公钥地址

/oauth/token_key,两者返回的格式不同,请注意区分。

3、在请求/oauth/tonen和/oauth/token_key时,默认需要Basic认证,即需要在请求中添加client_id和client_security参数。

源地址:

吉蒂:

GitHub:

© 版权声明
THE END
喜欢就支持一下吧
点赞126赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容