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流程图(个人理解)

springsecurity实现单点登录_第1张图片

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 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);
    }

}

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();
    }

}

你可能感兴趣的:(java,java,ajax,前端,kafka,运维)