• 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    **第二步:**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
    
    • 1
    • 2
    • 3
    • 4
    • 5

    **第三步:**获取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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

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

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

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

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

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

    第九步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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146

    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;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    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);
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    获取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 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 userInfoMap = new HashMap<>(1);
            userInfoMap.put("access_token",accessToken);
            return HttpUtil.doGet(GET_USER_INFO_URL, userInfoMap);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    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')");
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    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();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
  • 相关阅读:
    【7.27】代码源 - 【删数】【括号序列】【数字替换】【游戏】【画画】
    PostgreSQL文本搜索(一)——简介
    JS 正则表达式常用方法
    Qt 视口和窗口的区别
    钉钉如何通过AppLink快速连接仓储系统
    Kinsoku jikou desu新浪股票接口变动(php)
    RHCSA认证考试---11.查找文件
    研究 | CT图像迭代重建算法研究进展
    【小笔记】基于SpringBoot使用WebSocket进行前后端通信
    Regular Paper, Short Paper, Demo Paper 和Poster的区别
  • 原文地址:https://blog.csdn.net/m0_67401660/article/details/126516462