springsecurity实现单点登录_springsecurity单点登录-程序员宅基地

技术标签: springsecurity  

小伙伴们,你们好呀!我是老寇!废话不多说,跟我一起学习单点登录SSO

目录

1.运行效果图(b站-地址)

2. 老寇云SSO架构

3.老寇云SSO授权模式

4.老寇云SSO流程图(个人理解)

5.老寇云SSO流程说明(个人理解) 

6.核心代码

1.运行效果图(b站-地址

springsecurity单点登录

2. 老寇云SSO架构

1.基础框架:springboot + springcloud

2.认证授权:shiro + jwt (im-sso)、springsecurity + oauth2(security-auth 和 security-server

3.缓存:redis

3.老寇云SSO授权模式

老寇云采用的主要是授权码模式和密码模式 

security-auth采用密码模式

  • grant_type:表示授权类型,此处的值固定为"password",必选项。
  • username:表示用户名,必选项。
  • password:表示用户的密码,必选项。
  • scope:表示权限范围,可选项。
  • client_id:表示客户端的ID,可选
  • client_secret:表示客户端的密钥,可选

security-server采用授权码模式 

  • response_type:表示授权类型,必选项,此处的值固定为"code"
  • client_id:表示客户端的ID,必选项
  • redirect_uri:表示重定向URI,可选项
  • scope:表示申请的权限范围,可选项
  • state:表示客户端的当前状态,可以指定任意值,可选项.认证服务器会原封不动地返回这个值。

4.老寇云SSO流程图(个人理解)

5.老寇云SSO流程说明(个人理解) 

第一步:老寇云加载页发送POST请求并携带client_id、client_secret、grant_type、username、password参数到security-auth获取token

POST   https://1.com/auth/laokou-demo/oauth/token
grant_type: password
username: nBG5ht
password: 123
scope: auth
client_id: client_auth
client_secret: secret

第二步:security-auth拿到code请求security-server获取access_token

POST   http://localhost:9028/laokou-demo/oauth/token
client_id: client_auth
client_secret: secret
redirect_uri: https://1.com/im/loading.html
grant_type: authorization_code

第三步:获取token失败,授权码已被使用

第四步:响应前端授权码已被使用

第五步:发送GET请求并携带参数请求security-server服务

GET   http://localhost:9028/laokou-demo/oauth/authorize
response_type: code
client_id: client_auth
redirect_uri: https://1.com/im/loading.html
scope: userInfo
state: 123

第六步:如果没有登录,输入账号密码进行登录或登录未过期获取授权码code,并重定向到老寇云加载页

第七步:重复第一步的步骤

第八步:重复第二步的步骤

第九步:授权码可用,获取access_token

        第九步1:用拿到的access_token请求security-server的资源服务,获取userKey

GET   http://localhost:9028/laokou-demo/userKey
access_token: dsfdsf233

        第九步2:security-server响应userKey

第十步:用拿到的userKey,获取你所要对接系统的token生成接口,这里是去请求im-sso生成授权码token

第十一步:im-sso生成授权码token响应给security-auth

第十二步:security-auth将授权码token响应给老寇云加载页

第十三步:老寇云加载页拿到token并跳转到老寇云首页

第十四步:老寇云首页验证token有效性,token过期又跳转到老寇云加载页

6.核心代码

security-auth的yml配置

sso:
  token:
    client_id: client_auth
    client_secret: secret
    redirect_uri: https://1.com/im/loading.html
    grant_type: authorization_code

security-auth核心配置类

package io.laokou.auth.config;

import io.laokou.auth.token.RenTokenEnhancer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;

/**
 * TODO
 *
 * @author Kou Shenhai 2413176044
 * @version 1.0
 * @date 2021/5/28 0028 下午 4:53
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private WebResponseExceptionTranslator webResponseExceptionTranslator;
    @Autowired
    private PasswordEncoder passwordEncoder;
    /**
     * 配置客户端信息
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //in-memory存储
        clients.inMemory()
                //有一些不需要配置,你可以对照文档去弄
                .withClient("client_auth")
                //授权类型
                .authorizedGrantTypes("password")
                .scopes("auth")
                .secret("secret")
                .autoApprove(true);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST, HttpMethod.DELETE);
        //密码模式
        endpoints.authenticationManager(authenticationManager);
        //令牌增强
        endpoints.tokenEnhancer(tokenEnhancer());
        //登录或者鉴权失败时的返回信息
        endpoints.exceptionTranslator(webResponseExceptionTranslator);
    }

    @Bean
    public TokenEnhancer tokenEnhancer() {
        return new RenTokenEnhancer();
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security.allowFormAuthenticationForClients()
                .passwordEncoder(passwordEncoder)
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }

}
package io.laokou.auth.config;

import io.laokou.auth.filter.ValidateCodeFilter;
import io.laokou.auth.provider.AuthAuthenticationProvider;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * Spring Security配置
 * @author Kou Shenhai 2413176044
 * @version 1.0
 * @date 2021/5/28 0028 上午 10:33
 */
@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private ValidateCodeFilter validateCodeFilter;
    @Autowired
    private AuthAuthenticationProvider authAuthenticationProvider;

    /**
     * 密码模式
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception{
        return super.authenticationManager();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //设置自定义认证
        auth.authenticationProvider(authAuthenticationProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                .requestMatchers().anyRequest()
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/authorize").permitAll();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(HttpMethod.OPTIONS);
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }
}

security-auth的重写token生成逻辑 

package io.laokou.auth.token;

import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;

/**
 * TODO
 *
 * @author Kou Shenhai 2413176044
 * @version 1.0
 * @date 2021/5/28 0028 下午 5:13
 */
public class RenTokenEnhancer implements TokenEnhancer {

    @Autowired
    private 自己写的生成token的工具类 工具类实例;

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication auth) {
        if (accessToken instanceof DefaultOAuth2AccessToken) {
            DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
            //添加授权码
            String userKey = auth.getUserAuthentication().getPrincipal().toString();
            token.setValue(工具类实例.getAuthorize(userKey));
            //2秒过后重新认证 -> 本系统只依赖于工具类生成的token,不依赖于springsecurity的token,这么做是方便token过期后,springsecurity这边不能认证的情况(因为springsecurity的token未过期,就不会给你进行重新登录,如果感兴趣可以去读一下源码)
            token.setExpiration(DateTime.now().plusSeconds(2).toDate());
            return token;
        }
        return accessToken;
    }

}

 security-auth拦截器

package io.laokou.auth.filter;

import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * TODO
 * @author Kou Shenhai 2413176044
 * @version 1.0
 * @date 2021/4/11 0011 下午 2:29
 */
@Component
@AllArgsConstructor
public class ValidateCodeFilter extends OncePerRequestFilter {

    private final static AntPathMatcher antPathMatcher = new AntPathMatcher();

    private final static String OAUTH_URL = "/oauth/token";

    private final static String GRANT_TYPE = "password";

    @SneakyThrows
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
        if (antPathMatcher.match(request.getServletPath(), OAUTH_URL)
                && request.getMethod().equalsIgnoreCase("POST")
                && GRANT_TYPE.equals(request.getParameter("grant_type"))) {
            filterChain.doFilter(request, response);
        }
    }

}

获取token的工具类

package io.laokou.auth.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.laokou.auth.exception.RenAuthenticationException;
import io.laokou.common.utils.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * TODO
 *
 * @author Kou Shenhai 2413176044
 * @version 1.0
 * @date 2021/4/11 0011 下午 5:16
 */
@Component
@Slf4j
public class AuthUtil {

    @Value("${sso.token.client_id}")
    private String CLIENT_ID;

    @Value("${sso.token.client_secret}")
    private String CLIENT_SECRET;

    @Value("${sso.token.redirect_uri}")
    private String REDIRECT_URI;

    @Value("${sso.token.grant_type}")
    private String GRANT_TYPE;

    private static final String POST_AUTHORIZE_URL = "http://localhost:9028/laokou-demo/oauth/token";

    private static final String GET_USER_INFO_URL = "http://localhost:9028/laokou-demo/userKey";

    public String getAccessToken(String code) throws IOException {
        //将code放入
        Map<String,String> tokenMap = new HashMap<>(5);
        tokenMap.put("code",code);
        tokenMap.put("client_id",CLIENT_ID);
        tokenMap.put("client_secret",CLIENT_SECRET);
        tokenMap.put("redirect_uri",REDIRECT_URI);
        tokenMap.put("grant_type",GRANT_TYPE);
        //根据自己的请求方式,可以自己去写httpclient,你自己去弄
        String accessToken = HttpUtil.doPost(POST_AUTHORIZE_URL,tokenMap);
        if (StringUtils.isEmpty(accessToken)){
            throw new RenAuthenticationException("授权码已过期,请重新获取");
        }
        JSONObject jsonObject = JSON.parseObject(accessToken);
        return jsonObject.getString("access_token");
    }

    public String getUserKey(String accessToken) throws IOException {
        Map<String,String> userInfoMap = new HashMap<>(1);
        userInfoMap.put("access_token",accessToken);
        return HttpUtil.doGet(GET_USER_INFO_URL, userInfoMap);
    }

}

security-auth认证逻辑实现

package io.laokou.auth.provider;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.ArrayList;

/**
 * TODO
 *
 * @author Kou Shenhai 2413176044
 * @version 1.0
 * @date 2021/4/16 0016 上午 9:45
 */
@Component
@Slf4j
public class AuthAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private AuthUtil authUtil;

    @SneakyThrows
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String code = authentication.getName();
        String password = (String)authentication.getCredentials();
        log.info("code:{}",code);
        String accessToken = authUtil.getAccessToken(code);
        //自己改造获取token的逻辑 -> 懒得写啦,你自己弄
        String userKey = authUtil.getUserKey(accessToken);
        if (StringUtils.isEmpty(userKey)) {
            throw new Exception("账户不存在");
        }
        UserDetails userDetails = new User( userKey,password,new ArrayList<>());
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails.getUsername(),authentication.getCredentials(),userDetails.getAuthorities());
        authenticationToken.setDetails(authentication.getDetails());
        return authenticationToken;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

 security-server配置和上面类似,唯一的就是多了一个资源的服务,这个需要通过用access_token才能访问资源

package io.laokou.security.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

/**
 * TODO
 *
 * @author Kou Shenhai 2413176044
 * @version 1.0
 * @date 2021/4/16 0016 下午 12:50
 */
public class ResourceServerConfig {

    @Configuration()
    @EnableResourceServer()
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.requestMatchers()
                    .antMatchers("/userKey")
                    .and()
                    .authorizeRequests().antMatchers().authenticated()
                    .and()
                    .authorizeRequests().antMatchers("/userKey")
                    .access("#oauth2.hasScope('userInfo')");
        }
    }

}

security-server获取的用户唯一标识,通过这个唯一标识获取IM系统的授权码token

package io.laokou.security.controller;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;

/**
 * TODO
 *
 * @author Kou Shenhai 2413176044
 * @version 1.0
 * @date 2021/4/16 0016 上午 11:19
 */
@RestController
public class ResourceController {

    /**
     * 唯一标识
     * @param principal
     * @return
     */
    @GetMapping("/userKey")
    @CrossOrigin
    public String getUserKey(Principal principal) {
        return principal.getName();
    }

}

开源地址:https://gitee.com/tttt_wmh_cn/KCloud-Platform

演示地址:http://175.178.69.253

  • admin/admin123
  • test/test123
  • laok5/test123
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_39893313/article/details/118660073

智能推荐

IOS--UIDatePicker 时间选择器 的使用方法详细-程序员宅基地

文章浏览阅读509次。IOS--UIDatePicker 时间选择器 的使用方法详细 // 主要有下面四种类型: // 日期显示、// 日期和时间显示、// 时间显示、// 倒计时选择 // UIDatePicker控件的常用方法 时间选择控件 UIDatePicker *oneDatePicker = [[UIDatePicker alloc] i..._uidatepicker date是0点

官方教程:如何创建您的第一个Java应用程序_启动前构建工件是什么意思-程序员宅基地

文章浏览阅读803次。创建您的第一个Java应用程序在本教程中,您将学习如何创建,运行和打包简单Java应用程序。在此过程中,您将熟悉IntelliJ IDEA功能以提高开发人员的工作效率:编码帮助和辅助工具。创建一个新项目点击下载IntelliJ IDEA最新试用版安装JDK要在IntelliJ IDEA中开发Java应用程序,您需要Java SDK(JDK)。如果您的计算机上未安装Java,则需要下载JDK软件包。 打开jdk.java.net网站。 在那里,您可以找到Windows,mac.._启动前构建工件是什么意思

6种Java数组遍历方法与JavaScript的12种循环遍历方法-程序员宅基地

文章浏览阅读830次。1、for 循环let arr = \[1,2,3\];for (let i=0; iconsole.log(i,arr\[i\])}// 0 1// 1 2// 2 3for 循环是 Js 中最常用的一个循环工具,经常用于数组的循环遍历。2、for in 循环let obj = {name:'zhou',age:'**'}for(let i in obj){console.log(i,obj\[..._请写出两个java script循环遍历数组的方法

解决putty远程连接Linux拒绝访问的问题NetWork error:Connection refused_putty远程连接ssh拒绝访问-程序员宅基地

文章浏览阅读5.6k次,点赞6次,收藏11次。远程连接出现拒绝访问,一般情况是没有安装ssh 服务,新装的系统需要自己手动安装ssh-server第一步:检查linux系统是否安装了ssh-server在终端中输入:ssh localhost出现:ssh: connect to host localhost port 22: Connection refused说明没有安装,需手动安装 SSH。第二步:安装 SSH在终端中输入:sudo apt-get install openssh-server第三步:检测ssh服务是否启用在终端中输_putty远程连接ssh拒绝访问

CSS 字体 font-family属性_css font-family-程序员宅基地

文章浏览阅读8.5k次,点赞4次,收藏40次。字体CSS规范清楚的认识到,字体选择是一个常见而且很重要的特性,所以设置字体的属性就是样式表中最常见的用途之一。字体相关的属性在CSS1就已经定义,CSS3又新增了font-stretch 和 font-size-adjust 这两个属性。人们早已认识到字体选择很重要,并在CSS2就支持可下载字体,也定义了 @font-face 相关属性,但是并没有得到浏览器的广泛支持。直到CSS3,浏览器才开始..._css font-family

机器学习/数据挖掘之中国大牛_数据挖掘算法中国人-程序员宅基地

文章浏览阅读1.1k次。开学以来,由于课程压力比较大,加上自己在参加一些项目,未能及时更新博客。深有荒废之感,只能多积累,暂时只输入,不输出,等下学期时间充足了再多写博客。转载自:http://blog.csdn.net/playoffs/article/details/7588597推荐几个机器学习和数据挖掘领域相关的中国大牛:李航:http://research.microsoft.com/e_数据挖掘算法中国人

随便推点

测试开发工程师常见面试题----Linux_测试工程师面试linux-程序员宅基地

文章浏览阅读647次。1. 工作中常用的 Linux 命令有哪些?2.什么命令可以帮助 Linux 执行 Windows 上传的脚本?改变编码格式 vim test.sh :set ff?// 显示dos的话 :set ff=unix:wq3简述 Linux 三剑客 grep 命令 根据用户指定的模式 pattern 对目标文本进行过滤,显示被模式匹配到的行; grep [options] pattern [file] 常用参数: -v 显示不被pattern匹配到的行 ..._测试工程师面试linux

c语言 动态分配存储空间_分配存储空间c语言语句-程序员宅基地

文章浏览阅读2.6k次。三种内置函数:为指针动态分配内存之后,指针就变成了数组。返回数组:malloc前面需要强转,且有一个参数;calloc不需要,且有两个参数。1. malloc:2. calloc:3. ralloc:..._分配存储空间c语言语句

偷得浮生半日闲-程序员宅基地

文章浏览阅读80次。感觉时间越来越不够用了,上午看到了李锟老师在CSDN的AJAX技术讨论聊天室,本想去看看,却发现只能看到别人和李锟老师的话,我却无法说一个字。有话不能讲的感觉真郁闷,我还是自己写文章去了…… 不过为什么会发生这个情况呢?有没有哪位朋友可以把我的问题反馈给CSDN一下呢? ..._偷得浮生半日闲 csdn

String跟StringBuffer的区别_string与stringbuffer最大的区别-程序员宅基地

文章浏览阅读1.3k次。区别一:在Java中字符串使用String类进行表示,但是String类表示字符串有一个最大的问题:“字符串常量一旦声明则不可改变,而字符串对象可以改变,但是改变的是其内存地址的指向。”所以String类不适合于频繁修改的字符串操作上,所以在这种情况下,往往可以使用StringBuffer类,即StringBuffer类方便用户进行内容修改,区别二:在String类中使用“+”作为数据的连接操作,而在StringBuffer类中使用append()方法(方法定义:public StringBuffer a_string与stringbuffer最大的区别

dataTable的页面刷新操作_dtable刷新-程序员宅基地

文章浏览阅读1.5w次,点赞2次,收藏5次。页面初始化DataTable的时候,请求参数往往是封装在dataTable里面的,刷新表格数据的时候重新注入参数会很麻烦。版主碰到这个问题后,试过了很多方法,最终还是在官方API例找到了例子,即draw()方法。具体代码如下:draw()var table = $('#example').DataTable();_dtable刷新

Lightoj1282——Leading and Trailing(幂取模求前三位)_求前三位pow(10,)-程序员宅基地

文章浏览阅读803次。Description You are given two integers: n and k, your task is to find the most significant three digits, and least significant three digits of nk.Input Input starts with an integer T (≤ 1000), denoti_求前三位pow(10,)

推荐文章

热门文章

相关标签