【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架

前言

博客分享JWT,令牌配置,JWT实现SSO单点登录


一、SpringSecurityOAuth

【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第1张图片

  • 实现如下效果:
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第2张图片
  • 实现OAuth服务提供商功能:
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第3张图片

实现一个标准的OAuth2协议中Provider角色的主要功能

  • 创建成功处理器与失败处理器
package com.zcw.security.app.authentication;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zcw.security.core.support.SimpleResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @ClassName : ZcwAuthenctiationFailureHandler
 * @Description : APP环境下认证失败处理器
 * @Author : Zhaocunwei
 * @Date: 2020-06-30 16:30
 */
@Component("zcwAuthenctiationFailureHandler")
@Slf4j
public class ZcwAuthenctiationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    @Autowired
    private ObjectMapper objectMapper;

    /* (non-Javadoc)
     * @see org.springframework.security.web.authentication.AuthenticationFailureHandler#onAuthenticationFailure(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.springframework.security.core.AuthenticationException)
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {

        logger.info("登录失败");

        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(new SimpleResponse(exception.getMessage())));

    }

}


package com.zcw.security.app.authentication;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @ClassName : ZcwAuthenticationSuccessHandler
 * @Description : APP环境下认证成功处理器
 * @Author : Zhaocunwei
 * @Date: 2020-06-30 16:39
 */
@Component("zcwAuthenticationSuccessHandler")
@Slf4j
public class ZcwAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private AuthorizationServerTokenServices authorizationServerTokenServices;

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.security.web.authentication.
     * AuthenticationSuccessHandler#onAuthenticationSuccess(javax.servlet.http.
     * HttpServletRequest, javax.servlet.http.HttpServletResponse,
     * org.springframework.security.core.Authentication)
     */
    @SuppressWarnings("unchecked")
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {

        logger.info("登录成功");

        String header = request.getHeader("Authorization");

        if (header == null || !header.startsWith("Basic ")) {
            throw new UnapprovedClientAuthenticationException("请求头中无client信息");
        }

        String[] tokens = extractAndDecodeHeader(header, request);
        assert tokens.length == 2;

        String clientId = tokens[0];
        String clientSecret = tokens[1];

        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);

        if (clientDetails == null) {
            throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在:" + clientId);
        } else if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) {
            throw new UnapprovedClientAuthenticationException("clientSecret不匹配:" + clientId);
        }

        TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "custom");

        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);

        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);

        OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);

        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(token));

    }

    private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException {

        byte[] base64Token = header.substring(6).getBytes("UTF-8");
        byte[] decoded;
        try {
            decoded = Base64.decode(base64Token);
        } catch (IllegalArgumentException e) {
            throw new BadCredentialsException("Failed to decode basic authentication token");
        }

        String token = new String(decoded, "UTF-8");

        int delim = token.indexOf(":");

        if (delim == -1) {
            throw new BadCredentialsException("Invalid basic authentication token");
        }
        return new String[] { token.substring(0, delim), token.substring(delim + 1) };
    }

}


  • 创建简单响应的封装类
package com.zcw.security.core.support;

/**
 * @ClassName : SimpleResponse
 * @Description : 简单响应的封装类
 * @Author : Zhaocunwei
 * @Date: 2020-06-30 16:33
 */
public class SimpleResponse {
    public SimpleResponse(Object content){
        this.content = content;
    }

    private Object content;

    public Object getContent() {
        return content;
    }

    public void setContent(Object content) {
        this.content = content;
    }
}


  • 创建配置类,注入OAuth相关信息
package com.zcw.security.app.authentication;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;

/**
 * @ClassName : ZcwAuthorizationServerConfig
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-06-30 16:37
 */
@Configuration
@EnableAuthorizationServer  //添加此注解,就实现了认证服务器
public class ZcwAuthorizationServerConfig {
    /**
     * 如果实现了认证服务器,就可以实现4种授权模式,
     * 什么也不配置,就相当于开启了
     */
    //授权码模式

}


-测试
【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第4张图片
我们在请求时,默认需要添加一个角色就是“ROLE_USER”
【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第5张图片

package com.zcw.security;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.social.security.SocialUser;
import org.springframework.social.security.SocialUserDetails;
import org.springframework.social.security.SocialUserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * @ClassName : DemoUserDetailsService
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-06-30 16:50
 */
@Component
@Transactional
@Slf4j
public class DemoUserDetailsService implements UserDetailsService, SocialUserDetailsService {
    @Autowired
    private PasswordEncoder passwordEncoder;


    /*
     * (non-Javadoc)
     *
     * @see org.springframework.security.core.userdetails.UserDetailsService#
     * loadUserByUsername(java.lang.String)
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//		logger.info("表单登录用户名:" + username);
//		Admin admin = adminRepository.findByUsername(username);
//		admin.getUrls();
//		return admin;
        return buildUser(username);
    }

    @Override
    public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException {
        log.info("设计登录用户Id:" + userId);
        return buildUser(userId);
    }

    private SocialUserDetails buildUser(String userId) {
        // 根据用户名查找用户信息
        //根据查找到的用户信息判断用户是否被冻结
        String password = passwordEncoder.encode("123456");
        log.info("数据库密码是:"+password);

        return new SocialUser(userId, password,
                true, true, true, true,
                AuthorityUtils.commaSeparatedStringToAuthorityList("xxx"));
    }

}


  • 配置我们服务提供商,需要在配置文件里面编写
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第6张图片
oauth2:
    client:
      id: zcw
      client-secret: zcwsecret

  • 启动访问官方提供的测试接口
  • 在这里插入图片描述
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第7张图片
  • 搭建资源服务器,一个注解搞定
package com.zcw.security.app.authentication;

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

/**
 * @ClassName : ZcwResourceServerConfig
 * @Description : 资源服务器
 * @Author : Zhaocunwei
 * @Date: 2020-06-30 17:15
 */
@Configuration
@EnableResourceServer
public class ZcwResourceServerConfig {

}


  • Oauth核心流程
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第8张图片

二、重构代码支持Token

【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第9张图片
【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第10张图片

package com.zcw.security.app.authentication;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @ClassName : ZcwAuthenticationSuccessHandler
 * @Description : APP环境下认证成功处理器
 * @Author : Zhaocunwei
 * @Date: 2020-06-30 16:39
 */
@Component("zcwAuthenticationSuccessHandler")
@Slf4j
public class ZcwAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private AuthorizationServerTokenServices authorizationServerTokenServices;

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.security.web.authentication.
     * AuthenticationSuccessHandler#onAuthenticationSuccess(javax.servlet.http.
     * HttpServletRequest, javax.servlet.http.HttpServletResponse,
     * org.springframework.security.core.Authentication)
     */
    @SuppressWarnings("unchecked")
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {

        logger.info("登录成功");

        String header = request.getHeader("Authorization");

        if (header == null || !header.startsWith("Basic ")) {
            throw new UnapprovedClientAuthenticationException("请求头中无client信息");
        }

        String[] tokens = extractAndDecodeHeader(header, request);
        assert tokens.length == 2;

        String clientId = tokens[0];
        String clientSecret = tokens[1];

        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);

        if (clientDetails == null) {
            throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在:" + clientId);
        } else if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) {
            throw new UnapprovedClientAuthenticationException("clientSecret不匹配:" + clientId);
        }

        TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "custom");

        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);

        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);

        OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);

        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(token));

    }

    private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException {

        byte[] base64Token = header.substring(6).getBytes("UTF-8");
        byte[] decoded;
        try {
            decoded = Base64.decode(base64Token);
        } catch (IllegalArgumentException e) {
            throw new BadCredentialsException("Failed to decode basic authentication token");
        }

        String token = new String(decoded, "UTF-8");

        int delim = token.indexOf(":");

        if (delim == -1) {
            throw new BadCredentialsException("Invalid basic authentication token");
        }
        return new String[] { token.substring(0, delim), token.substring(delim + 1) };
    }

}


  • 简化模式
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第11张图片
  • 授权码模式
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第12张图片
package com.zcw.security.core.validate.code;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.stereotype.Component;

import javax.servlet.Filter;

/**
 * @ClassName : ValidateCodeSecurityConfig
 * @Description : 校验码相关安全配置
 * @Author : Zhaocunwei
 * @Date: 2020-07-01 13:31
 */
@Component("validateCodeSecurityConfig")
public class ValidateCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    @Autowired
    private Filter validateCodeFilter;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(validateCodeFilter, AbstractPreAuthenticatedProcessingFilter.class);
    }
}

package com.zcw.security.core.authentication;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import com.zcw.security.core.properties.SecurityConstants;

/**
 * @ClassName : FormAuthenticationConfig
 * @Description : 表单登录配置
 * @Author : Zhaocunwei
 * @Date: 2020-07-01 13:33
 */
@Component
public class FormAuthenticationConfig {
    @Autowired
    protected AuthenticationSuccessHandler zcwAuthenticationSuccessHandler;

    @Autowired
    protected AuthenticationFailureHandler zcwAuthenticationFailureHandler;

    public void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
                .loginProcessingUrl(SecurityConstants.DEFAULT_SIGN_IN_PROCESSING_URL_FORM)
                .successHandler(zcwAuthenticationSuccessHandler)
                .failureHandler(zcwAuthenticationFailureHandler);
    }

}


package com.zcw.security.app.authentication;

import com.zcw.security.app.authentication.openid.OpenIdAuthenticationSecurityConfig;
import com.zcw.security.core.authentication.AuthorizeConfigManager;
import com.zcw.security.core.authentication.FormAuthenticationConfig;
import com.zcw.security.core.validate.code.ValidateCodeSecurityConfig;
import com.zcw.security.core.validate.code.sms.SmsCodeAuthenticationSecurityConfig;
import org.springframework.beans.factory.annotation.Autowired;
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;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.social.security.SpringSocialConfigurer;



/**
 * @ClassName : ZcwResourceServerConfig
 * @Description : 资源服务器
 * @Author : Zhaocunwei
 * @Date: 2020-06-30 17:15
 */
@Configuration
@EnableResourceServer
public class ZcwResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    protected AuthenticationSuccessHandler zcwAuthenticationSuccessHandler;

    @Autowired
    protected AuthenticationFailureHandler zcwAuthenticationFailureHandler;

    @Autowired
    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;

    @Autowired
    private OpenIdAuthenticationSecurityConfig openIdAuthenticationSecurityConfig;

    @Autowired
    private ValidateCodeSecurityConfig validateCodeSecurityConfig;

    @Autowired
    private SpringSocialConfigurer zcwSocialSecurityConfig;

    @Autowired
    private AuthorizeConfigManager authorizeConfigManager;

    @Autowired
    private FormAuthenticationConfig formAuthenticationConfig;

    @Override
    public void configure(HttpSecurity http) throws Exception {

        formAuthenticationConfig.configure(http);

        http.apply(validateCodeSecurityConfig)//验证码的安全配置
                .and()
                .apply(smsCodeAuthenticationSecurityConfig)//短信验证码的安全配置
                .and()
                .apply(zcwSocialSecurityConfig)//社交登录的安全配置
                .and()
                .apply(openIdAuthenticationSecurityConfig)
                .and()
                .csrf().disable();

        authorizeConfigManager.config(http.authorizeRequests());
    }
}


  • 自定义认证处理的过滤器

package com.zcw.security.app.authentication.openid;

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

import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;

import com.zcw.security.core.properties.SecurityConstants;

/**
 * @ClassName : OpenIdAuthenticationFilter
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-01 12:45
 */
public class OpenIdAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    // ~ Static fields/initializers
    // =====================================================================================

    private String openIdParameter = SecurityConstants.DEFAULT_PARAMETER_NAME_OPENID;
    private String providerIdParameter = SecurityConstants.DEFAULT_PARAMETER_NAME_PROVIDERID;
    private boolean postOnly = true;

    // ~ Constructors
    // ===================================================================================================

    public OpenIdAuthenticationFilter() {
        super(new AntPathRequestMatcher(SecurityConstants.DEFAULT_SIGN_IN_PROCESSING_URL_OPENID, "POST"));
    }

    // ~ Methods
    // ========================================================================================================

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        String openid = obtainOpenId(request);
        String providerId = obtainProviderId(request);

        if (openid == null) {
            openid = "";
        }
        if (providerId == null) {
            providerId = "";
        }

        openid = openid.trim();
        providerId = providerId.trim();

        OpenIdAuthenticationToken authRequest = new OpenIdAuthenticationToken(openid, providerId);

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);

        return this.getAuthenticationManager().authenticate(authRequest);
    }


    /**
     * 获取openId
     */
    protected String obtainOpenId(HttpServletRequest request) {
        return request.getParameter(openIdParameter);
    }

    /**
     * 获取提供商id
     */
    protected String obtainProviderId(HttpServletRequest request) {
        return request.getParameter(providerIdParameter);
    }

    /**
     * Provided so that subclasses may configure what is put into the
     * authentication request's details property.
     *
     * @param request
     *            that an authentication request is being created for
     * @param authRequest
     *            the authentication request object that should have its details
     *            set
     */
    protected void setDetails(HttpServletRequest request, OpenIdAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

    /**
     * Sets the parameter name which will be used to obtain the username from
     * the login request.
     *
     * @param usernameParameter
     *            the parameter name. Defaults to "username".
     */
    public void setOpenIdParameter(String openIdParameter) {
        Assert.hasText(openIdParameter, "Username parameter must not be empty or null");
        this.openIdParameter = openIdParameter;
    }


    /**
     * Defines whether only HTTP POST requests will be allowed by this filter.
     * If set to true, and an authentication request is received which is not a
     * POST request, an exception will be raised immediately and authentication
     * will not be attempted. The unsuccessfulAuthentication() method
     * will be called as if handling a failed authentication.
     * 

* Defaults to true but may be overridden by subclasses. */ public void setPostOnly(boolean postOnly) { this.postOnly = postOnly; } public final String getOpenIdParameter() { return openIdParameter; } public String getProviderIdParameter() { return providerIdParameter; } public void setProviderIdParameter(String providerIdParameter) { this.providerIdParameter = providerIdParameter; } }

  • 封装登录信息
package com.zcw.security.app.authentication.openid;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;

import java.util.Collection;

/**
 * @ClassName : OpenIdAuthenticationToken
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-01 12:46
 */
public class OpenIdAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    // ~ Instance fields
    // ================================================================================================

    private final Object principal;
    private String providerId;

    // ~ Constructors
    // ===================================================================================================

    /**
     * This constructor can be safely used by any code that wishes to create a
     * UsernamePasswordAuthenticationToken, as the {@link #isAuthenticated()}
     * will return false.
     *
     */
    public OpenIdAuthenticationToken(String openId, String providerId) {
        super(null);
        this.principal = openId;
        this.providerId = providerId;
        setAuthenticated(false);
    }

    /**
     * This constructor should only be used by AuthenticationManager or
     * AuthenticationProvider implementations that are satisfied with
     * producing a trusted (i.e. {@link #isAuthenticated()} = true)
     * authentication token.
     *
     * @param principal
     * @param credentials
     * @param authorities
     */
    public OpenIdAuthenticationToken(Object principal,
                                     Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        super.setAuthenticated(true); // must use super, as we override
    }

    // ~ Methods
    // ========================================================================================================

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return this.principal;
    }

    public String getProviderId() {
        return providerId;
    }

    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException(
                    "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        }

        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
    }
}


  • 验证我们Token信息
package com.zcw.security.app.authentication.openid;

import org.apache.commons.collections.CollectionUtils;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.security.SocialUserDetailsService;

import java.util.HashSet;
import java.util.Set;

/**
 * @ClassName : OpenIdAuthenticationProvider
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-01 13:36
 */
public class OpenIdAuthenticationProvider implements AuthenticationProvider {
    private SocialUserDetailsService userDetailsService;

    private UsersConnectionRepository usersConnectionRepository;

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.security.authentication.AuthenticationProvider#
     * authenticate(org.springframework.security.core.Authentication)
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        OpenIdAuthenticationToken authenticationToken = (OpenIdAuthenticationToken) authentication;

        Set<String> providerUserIds = new HashSet<>();
        providerUserIds.add((String) authenticationToken.getPrincipal());
        Set<String> userIds = usersConnectionRepository.findUserIdsConnectedTo(authenticationToken.getProviderId(), providerUserIds);

        if(CollectionUtils.isEmpty(userIds) || userIds.size() != 1) {
            throw new InternalAuthenticationServiceException("无法获取用户信息");
        }

        String userId = userIds.iterator().next();

        UserDetails user = userDetailsService.loadUserByUserId(userId);

        if (user == null) {
            throw new InternalAuthenticationServiceException("无法获取用户信息");
        }

        OpenIdAuthenticationToken authenticationResult = new OpenIdAuthenticationToken(user, user.getAuthorities());

        authenticationResult.setDetails(authenticationToken.getDetails());

        return authenticationResult;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.security.authentication.AuthenticationProvider#
     * supports(java.lang.Class)
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return OpenIdAuthenticationToken.class.isAssignableFrom(authentication);
    }

    public SocialUserDetailsService getUserDetailsService() {
        return userDetailsService;
    }

    public void setUserDetailsService(SocialUserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    public UsersConnectionRepository getUsersConnectionRepository() {
        return usersConnectionRepository;
    }

    public void setUsersConnectionRepository(UsersConnectionRepository usersConnectionRepository) {
        this.usersConnectionRepository = usersConnectionRepository;
    }

}


  • 配置类,配置上面自定义的类

package com.zcw.security.app.authentication.openid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.security.SocialUserDetailsService;
import org.springframework.stereotype.Component;

/**
 * @author zcw
 *
 */
@Component
public class OpenIdAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
	
	@Autowired
	private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
	
	@Autowired
	private AuthenticationFailureHandler imoocAuthenticationFailureHandler;
	
	@Autowired
	private SocialUserDetailsService userDetailsService;
	
	@Autowired
	private UsersConnectionRepository usersConnectionRepository;
	
	@Override
	public void configure(HttpSecurity http) throws Exception {
		
		OpenIdAuthenticationFilter OpenIdAuthenticationFilter = new OpenIdAuthenticationFilter();
		OpenIdAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
		OpenIdAuthenticationFilter.setAuthenticationSuccessHandler(imoocAuthenticationSuccessHandler);
		OpenIdAuthenticationFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);
		
		OpenIdAuthenticationProvider OpenIdAuthenticationProvider = new OpenIdAuthenticationProvider();
		OpenIdAuthenticationProvider.setUserDetailsService(userDetailsService);
		OpenIdAuthenticationProvider.setUsersConnectionRepository(usersConnectionRepository);
		
		http.authenticationProvider(OpenIdAuthenticationProvider)
			.addFilterAfter(OpenIdAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
		
	}

}


  • 创建后处理器,用来区分不同端用户登录
    SocialAuthenticationFilter后处理器,用于在不同环境下个性化社交登录的配置
package com.zcw.security.core.social;


import org.springframework.social.security.SocialAuthenticationFilter;

public interface SocialAuthenticationFilterPostProcessor {
    /**
     * @param socialAuthenticationFilter
     */
    void process(SocialAuthenticationFilter socialAuthenticationFilter);
}


  • 把上面的接口,注入到配置类里面,进行判断后处理器不等于空
package com.zcw.security.core.social;

import org.springframework.social.security.SocialAuthenticationFilter;
import org.springframework.social.security.SpringSocialConfigurer;

/**
 * @ClassName : ZcwSpringSocialConfigurer
 * @Description :继承默认的社交登录配置,加入自定义的后处理逻辑
 * @Author : Zhaocunwei
 * @Date: 2020-06-28 18:17
 */
public class ZcwSpringSocialConfigurer extends SpringSocialConfigurer {
    /**
     * object 就是我们放到过滤器链上的filter
     * @param object
     * @param 
     * @return
     */
    private String filterProcessesUrl;

    private SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor;

    public  ZcwSpringSocialConfigurer(String filterProcessesUrl) {
        this.filterProcessesUrl = filterProcessesUrl;
    }

    /* (non-Javadoc)
     * @see org.springframework.security.config.annotation.SecurityConfigurerAdapter#postProcess(java.lang.Object)
     */
    @SuppressWarnings("unchecked")
    @Override
    protected <T> T postProcess(T object) {
        SocialAuthenticationFilter filter = (SocialAuthenticationFilter) super.postProcess(object);
        filter.setFilterProcessesUrl(filterProcessesUrl);
        if (socialAuthenticationFilterPostProcessor != null) {
            socialAuthenticationFilterPostProcessor.process(filter);
        }
        return (T) filter;
    }

    public String getFilterProcessesUrl() {
        return filterProcessesUrl;
    }

    public void setFilterProcessesUrl(String filterProcessesUrl) {
        this.filterProcessesUrl = filterProcessesUrl;
    }

    public SocialAuthenticationFilterPostProcessor getSocialAuthenticationFilterPostProcessor() {
        return socialAuthenticationFilterPostProcessor;
    }

    public void setSocialAuthenticationFilterPostProcessor(
            SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor) {
        this.socialAuthenticationFilterPostProcessor = socialAuthenticationFilterPostProcessor;
    }

}


【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第13张图片
【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第14张图片

 @Bean
    public SpringSocialConfigurer zcwSocialSecurityConfig(){
        String filterPrpcessesUrl = mySecurityProperties.getSocialProperties().getFilterPrpcessesUrl();
        ZcwSpringSocialConfigurer configurer = new ZcwSpringSocialConfigurer(filterPrpcessesUrl);
        configurer.signupUrl(mySecurityProperties.getBrowserProperties()
        .getSingUpUrl());
        configurer.setSocialAuthenticationFilterPostProcessor(socialAuthenticationFilterPostProcessor);
        return  configurer;
    }

  • 后处理器接口实现,生成令牌返回给终端
package com.zcw.security.app.social.impl;

import com.zcw.security.core.social.SocialAuthenticationFilterPostProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.social.security.SocialAuthenticationFilter;
import org.springframework.stereotype.Component;

/**
 * @ClassName : AppSocialAuthenticationFilterPostProcessor
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-01 14:55
 */
@Component
public class AppSocialAuthenticationFilterPostProcessor implements SocialAuthenticationFilterPostProcessor {
    @Autowired
    private AuthenticationSuccessHandler  zcwAuthenticationSuccessHandler;
    @Override
    public void process(SocialAuthenticationFilter socialAuthenticationFilter) {
        socialAuthenticationFilter.setAuthenticationSuccessHandler(zcwAuthenticationSuccessHandler);
    }
}


  • 实现终端无session,操作

package com.zcw.security.app.social;

import com.zcw.security.app.AppSecretException;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionData;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

import java.util.concurrent.TimeUnit;

/**
 * @ClassName : AppSingUpUtils
 * @Description : app环境下替换providerSignInUtils,
 *              避免由于没有session导致读不到社交用户信息的问题
 * @Author : Zhaocunwei
 * @Date: 2020-07-01 15:05
 */
@Component
public class AppSingUpUtils {
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private UsersConnectionRepository usersConnectionRepository;

    @Autowired
    private ConnectionFactoryLocator connectionFactoryLocator;

    /**
     * 缓存社交网站用户信息到redis
     * @param request
     * @param connectionData
     */
    public void saveConnectionData(WebRequest request, ConnectionData connectionData) {
        redisTemplate.opsForValue().set(getKey(request), connectionData, 10, TimeUnit.MINUTES);
    }

    /**
     * 将缓存的社交网站用户信息与系统注册用户信息绑定
     * @param request
     * @param userId
     */
    public void doPostSignUp(WebRequest request, String userId) {
        String key = getKey(request);
        if(!redisTemplate.hasKey(key)){
            throw new AppSecretException("无法找到缓存的用户社交账号信息");
        }
        ConnectionData connectionData = (ConnectionData) redisTemplate.opsForValue().get(key);
        Connection<?> connection = connectionFactoryLocator.getConnectionFactory(connectionData.getProviderId())
                .createConnection(connectionData);
        usersConnectionRepository.createConnectionRepository(userId).addConnection(connection);

        redisTemplate.delete(key);
    }

    /**
     * 获取redis key
     * @param request
     * @return
     */
    private String getKey(WebRequest request) {
        String deviceId = request.getHeader("deviceId");
        if (StringUtils.isBlank(deviceId)) {
            throw new AppSecretException("设备id参数不能为空");
        }
        return "zcw:security:social.connect." + deviceId;
    }

}


package com.zcw.security.app;

/**
 * @ClassName : AppSecretException
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-01 15:10
 */
public class AppSecretException extends RuntimeException {
    public AppSecretException(String msg){
        super(msg);
    }
}


  • 修改配置类
package com.zcw.security.app.social;

import com.zcw.security.core.properties.SecurityConstants;
import com.zcw.security.core.social.ZcwSpringSocialConfigurer;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

/**
 * @ClassName : SpringSocialConfigurerPostProcessor
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-03 14:20
 */
 @Component
public class SpringSocialConfigurerPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    /* (non-Javadoc)
     * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String)
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(StringUtils.equals(beanName, "zcwSocialSecurityConfig")){
            ZcwSpringSocialConfigurer config = (ZcwSpringSocialConfigurer)bean;
            config.signupUrl(SecurityConstants.DEFAULT_SOCIAL_USER_INFO_URL);
            return config;
        }
        return bean;
    }
}


  • 创建我们的controller层
package com.zcw.security.app;

import com.zcw.security.app.social.AppSingUpUtils;
import com.zcw.security.browser.support.SocialUserInfo;
import com.zcw.security.core.properties.SecurityConstants;
import com.zcw.security.core.social.SocialController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;

import javax.servlet.http.HttpServletRequest;

/**
 * @ClassName : AppSecurityController
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-03 14:24
 */
@RestController
public class AppSecurityController {

    @Autowired
    private ProviderSignInUtils providerSignInUtils;

    @Autowired
    private AppSingUpUtils appSingUpUtils;

    /**
     * 需要注册时跳到这里,返回401和用户信息给前端
     * @param request
     * @return
     */
    @GetMapping(SecurityConstants.DEFAULT_SOCIAL_USER_INFO_URL)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public SocialUserInfo getSocialUserInfo(HttpServletRequest request) {
        Connection<?> connection = providerSignInUtils.getConnectionFromSession(new ServletWebRequest(request));
        //把用户信息存储到redis中
        appSingUpUtils.saveConnectionData(new ServletWebRequest(request), connection.createData());
        return SocialController.buildSocialUserInfo(connection);
    }
}


  • 创建抽象类:
package com.zcw.security.core.social;

import com.zcw.security.browser.support.SocialUserInfo;
import org.springframework.social.connect.Connection;

/**
 * @ClassName : SocialController
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-03 14:25
 */
public abstract class SocialController {
    /**
     * 根据Connection信息构建SocialUserInfo
     * @param connection
     * @return
     */
    public static SocialUserInfo buildSocialUserInfo(Connection<?> connection) {
        SocialUserInfo userInfo = new SocialUserInfo();
        userInfo.setProviderId(connection.getKey().getProviderId());
        userInfo.setProviderUserId(connection.getKey().getProviderUserId());
        userInfo.setNickname(connection.getDisplayName());
        userInfo.setHeadimg(connection.getImageUrl());
        return userInfo;
    }

}


三、令牌配置

【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第15张图片
针对上面的图片,进行自定义我们自己的Token相关操作:

  • 基本的Token参数配置
  • 使用JWT替换默认的token
  • 扩展和解析JWT的信息
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第16张图片
package com.zcw.security.app.authentication;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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;

/**
 * @ClassName : ZcwAuthorizationServerConfig
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-06-30 16:37
 */
@Configuration
@EnableAuthorizationServer  //添加此注解,就实现了认证服务器
public class ZcwAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;
    //进行修改,配置Token的入口点

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }

    /**
     * 配置跟客户端相关的一些配置信息---是指有哪些应用会访问我们服务
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("zcw")
                .secret("zcwsecret")
                //我们发出去的令牌有效时间是多少 单位为:秒
               .accessTokenValiditySeconds(7200)
               //支持哪些授权模式
               .authorizedGrantTypes("refresh_token","password")
               //发出的权限有哪些
               .scopes("all","read","write");
               .and()
                //配置另外一个客户端提供相应的配置
                .withClient("XXXXXXXXX");
    }
}


  • 把上面的类进行优化,抽象一下,查看可以给哪些客户端发送令牌

package com.zcw.security.core.properties;

import org.springframework.boot.autoconfigure.security.oauth2.OAuth2ClientProperties;

/**
 * @ClassName : OAuth2Properties
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-03 15:10
 */
public class OAuth2Properties {
    /**
     * 使用jwt时为token签名的秘钥
     */
    private String jwtSigningKey = "zcw";
    /**
     * 客户端配置
     */
    private OAuth2ClientProperties[] clients = {};

    public OAuth2ClientProperties[] getClients() {
        return clients;
    }

    public void setClients(OAuth2ClientProperties[] clients) {
        this.clients = clients;
    }

    public String getJwtSigningKey() {
        return jwtSigningKey;
    }

    public void setJwtSigningKey(String jwtSigningKey) {
        this.jwtSigningKey = jwtSigningKey;
    }
}


  • 认证服务器注册的第三方应用配置项
package com.zcw.security.core.properties;

/**
 * @ClassName : OAuth2ClientProperties
 * @Description : 认证服务器注册的第三方应用配置项
 * @Author : Zhaocunwei
 * @Date: 2020-07-03 15:11
 */
public class OAuth2ClientProperties {
    /**
     * 第三方应用appId
     */
    private String clientId;
    /**
     * 第三方应用appSecret
     */
    private String clientSecret;
    /**
     * 针对此应用发出的token的有效时间
     */
    private int accessTokenValidateSeconds = 7200;

    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public String getClientSecret() {
        return clientSecret;
    }

    public void setClientSecret(String clientSecret) {
        this.clientSecret = clientSecret;
    }

    public int getAccessTokenValidateSeconds() {
        return accessTokenValidateSeconds;
    }

    public void setAccessTokenValidateSeconds(int accessTokenValidateSeconds) {
        this.accessTokenValidateSeconds = accessTokenValidateSeconds;
    }

}


  • 创建一个顶级的配置文件
package com.zcw.security.core.properties;


/**
 * @ClassName : SecurityProperties
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-03 15:14
 */
@ConfigurationProperties(prefix = "zcw.security")
public class SecurityProperties {
    /**
     * 浏览器环境配置
     */
    private BrowserProperties browser = new BrowserProperties();
    /**
     * 验证码配置
     */
    private ValidateCodeProperties code = new ValidateCodeProperties();
    /**
     * 社交登录配置
     */
    private SocialProperties social = new SocialProperties();
    /**
     * OAuth2认证服务器配置
     */
    private OAuth2Properties oauth2 = new OAuth2Properties();

    public BrowserProperties getBrowser() {
        return browser;
    }

    public void setBrowser(BrowserProperties browser) {
        this.browser = browser;
    }

    public ValidateCodeProperties getCode() {
        return code;
    }

    public void setCode(ValidateCodeProperties code) {
        this.code = code;
    }

    public SocialProperties getSocial() {
        return social;
    }

    public void setSocial(SocialProperties social) {
        this.social = social;
    }

    public OAuth2Properties getOauth2() {
        return oauth2;
    }

    public void setOauth2(OAuth2Properties oauth2) {
        this.oauth2 = oauth2;
    }
}


  • 修改配置 文件
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第17张图片
  • 然后把我们的配置文件类,注入到我们认证服务器里:
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第18张图片
  • 创建令牌存取配置类:
package com.zcw.security.app;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

/**
 * @ClassName : TokenStoreConfig
 * @Description : 负责令牌的存取
 * @Author : Zhaocunwei
 * @Date: 2020-07-03 16:16
 */
@Configuration
public class TokenStoreConfig {
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
    /**
     * redis 配置类
     */
    @Bean
    public TokenStore redisTokenStore(){
        return new RedisTokenStore(redisConnectionFactory);
    }
}


  • 认证服务器里面添加redis的注入
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第19张图片

四、基于JWT实现SSO单点登录

  • 修改之前token类,添加JWT静态类:
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第20张图片
package com.zcw.security.app;

import com.zcw.security.core.properties.OAuth2Properties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

/**
 * @ClassName : TokenStoreConfig
 * @Description : 负责令牌的存取
 * @Author : Zhaocunwei
 * @Date: 2020-07-03 16:16
 */
@Configuration
public class TokenStoreConfig {
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
    /**
     * redis 配置类
     */
    @Bean
    public TokenStore redisTokenStore(){
        return new RedisTokenStore(redisConnectionFactory);
    }
    @Configuration
    @ConditionalOnProperty( prefix = "zcw.security.oauth2",
                          name = "storeType",
                          havingValue = "jwt",
                          matchIfMissing =true )
    public static class JwtTokenConfig{
        @Autowired
        private OAuth2Properties auth2Properties;
        @Bean
        public TokenStore jwtTokenStroe(){
            return new JwtTokenStore(jwtAccessTokenConverter());
        }
        //做一些Token生成处理
        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter(){
            //准备秘钥,进行签名
           JwtAccessTokenConverter  accessTokenConverter = new JwtAccessTokenConverter();
           //发出去需要这个秘钥,签名验证也需要这个签名
           accessTokenConverter.setSigningKey(auth2Properties.getJwtSigningKey());
           return accessTokenConverter;
        }
    }
}

【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第21张图片
【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第22张图片

  • 测试:
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第23张图片
  • JWT 扩展,编写实现类:
package com.zcw.security.app.jwt;

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;

import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName : ZcwJwtTokenEnhancer
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-04 14:26
 */
public class ZcwJwtTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,
                                     OAuth2Authentication authentication) {
        Map<String,Object> info= new HashMap<>();
        info.put("company","zcw");
        ((DefaultOAuth2AccessToken)accessToken).setAdditionalInformation(info);
        return accessToken;
    }
}


  • 放到配置类中:
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第24张图片
  • 配置认证服务器,使用我们扩展的JWT,增强器

【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第25张图片

 <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>

  • 验证签名
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第26张图片
  • 刷新token
    refresh_token是用来刷新token的,用户无感知的情况下:
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第27张图片
  • 基于JWT实现SSO
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第28张图片

创建新的模块-- 认证服务器 sso-demo

【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第29张图片

<!--项目版本管理-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.spring.platform</groupId>
                <artifactId>platform-bom</artifactId>
                <version>Brussels-SR4</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--添加springCloud依赖管理-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <!--添加maven的编译插件-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <!--对编译插件进行配置-->
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

  • 创建应用A与应用B
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第30张图片
  • sso-server添加依赖
<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.security.oauth</groupId>
			<artifactId>spring-security-oauth2</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-jwt</artifactId>
		</dependency>
	</dependencies>

  • 编写启动类:
package com.zcw.sso.server;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.swing.*;

/**
 * @ClassName : SsoServerApplication
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-04 15:19
 */
@SpringBootApplication
public class SsoServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SsoServerApplication.class,args);
    }
}


  • 创建配置类:(创建认证服务器)
package com.zcw.sso.server;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

/**
 * @ClassName : SsoAuthorizationServerConfig
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-04 15:28
 */
@Configuration
@EnableAuthorizationServer
public class SsoAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer)
        throws Exception{
        clientDetailsServiceConfigurer.inMemory()
                .withClient("zcw1")
                .secret("zcwsecrect1")
                .authorizedGrantTypes("authorization_code","refresh_token")
                .scopes("all")
                .and()
                .withClient("zcw2")
                .secret("zcwsecrect2")
                .authorizedGrantTypes("authorization_code","refresh_token")
                .scopes("all");
    }
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpointsConfigurer)
        throws Exception{
        endpointsConfigurer.tokenStore(jwtTokneStore())
                .accessTokenConverter(jwtAccessTokenConverter());
    }
    //认证服务器的安全配置
    @Override
    public void configure(AuthorizationServerSecurityConfigurer serverSecurityConfigurer)
        throws Exception{
        //添加授权表达式
        serverSecurityConfigurer.tokenKeyAccess("isAuthenticated()");
    }
    @Bean
    public TokenStore jwtTokneStore(){
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        //签名key
        converter.setSigningKey("zcw");
        return converter;
    }
}


  • 创建配置文件:

server:
  port: 9999
  context-path: /server
security:
  user:
    password: 123456

  • 编写应用A(应用B)

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
        </dependency>



  • 创建配置文件,使我们的授权生效
    添加认证服务器的地址
    【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第31张图片
security:
  oauth2:
    client:
      id: test
      client-secret: test
      # 认证服务器的地址
      user-authorization-uri: http://127.0.0.1:9999/server/oauth/authorize
      # 请求令牌的地址
      access-token-uri: http://127.0.0.1:9999/server/oauth/token
      #拿秘钥
    resource:
      jwt:
        key-uri: http://127.0.0.1:9999/server/oauth/token_key

server:
  port: 8099
  context-path: /clientA

package com.zcw.sso.client;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName : SsoClientAApplication
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-04 16:04
 */
@SpringBootApplication
@RestController
@EnableOAuth2Sso
public class SsoClientAApplication {
    @GetMapping("/user")
    public Authentication user(Authentication user){
        return user;
    }
    public static void main(String[] args) {
        SpringApplication.run(SsoClientAApplication.class,args);
    }
}


  • 访问clientA 直接跳转到clientB
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>SSO ClientA</title>
</head>
<body>
<h1>SSO Demo Client1</h1>
<a href="http://127.0.0.1:8060/clientB/index.html">访问ClientB</a>
</body>
</html>

【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第32张图片
clientB 与ClientA的编码除了端口,其他都一样

package client;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName : SsoClientAApplication
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-04 16:04
 */
@SpringBootApplication
@RestController
@EnableOAuth2Sso
public class SsoClientBApplication {
    @GetMapping("/user")
    public Authentication user(Authentication user){
        return user;
    }
    public static void main(String[] args) {
        SpringApplication.run(SsoClientBApplication.class,args);
    }
}


security:
  oauth2:
    client:
      id: test
      client-secret: test
      # 认证服务器的地址
      user-authorization-uri: http://127.0.0.1:9999/server/oauth/authorize
      # 请求令牌的地址
      access-token-uri: http://127.0.0.1:9999/server/oauth/token
      #拿秘钥
    resource:
      jwt:
        key-uri: http://127.0.0.1:9999/server/oauth/token_key

server:
  port: 8060
  context-path: /clientB

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
        </dependency>

【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第33张图片

  • 优化登录:标准的表单登
package com.zcw.sso.server;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @ClassName : SsoSecurityConfig
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-04 16:39
 */
@Configuration
public class SsoSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    //密码加密器
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    //覆盖默认配置 UserDetailsService
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                //密码加密器
                .passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
       http.formLogin()
               .and()
               .authorizeRequests()
               .anyRequest()
               .authenticated();
    }
}

package com.zcw.sso.server;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * @ClassName : SsoUserDetailsService
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-04 16:41
 */
@Component
public class SsoUserDetailsService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;
    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        return new User(username,passwordEncoder.encode("123456"),
                AuthorityUtils
                        .commaSeparatedStringToAuthorityList("ROLE_USER"));
    }
}


【Spring Security技术栈开发企业级认证与授权】-----------Spring Security OAuth开发APP认证框架_第34张图片

  • 优化授权-默认操作,用户无感知操作
    需要修改源代码使整个表单默认,自动提交
package com.zcw.sso.server;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @ClassName : SsoApprovalEndpoint
 * @Description :
 * @Author : Zhaocunwei
 * @Date: 2020-07-04 16:52
 */
@RestController
@SessionAttributes("authorizationRequest")
public class SsoApprovalEndpoint {
    @RequestMapping("/oauth/confirm_access")
    public ModelAndView getAccessConfirmation(Map<String, Object> model, HttpServletRequest request) throws Exception {
        String template = createTemplate(model, request);
        if (request.getAttribute("_csrf") != null) {
            model.put("_csrf", request.getAttribute("_csrf"));
        }
        return new ModelAndView(new SsoSpelView(template), model);
    }

    protected String createTemplate(Map<String, Object> model, HttpServletRequest request) {
        String template = TEMPLATE;
        if (model.containsKey("scopes") || request.getAttribute("scopes") != null) {
            template = template.replace("%scopes%", createScopes(model, request)).replace("%denial%", "");
        }
        else {
            template = template.replace("%scopes%", "").replace("%denial%", DENIAL);
        }
        if (model.containsKey("_csrf") || request.getAttribute("_csrf") != null) {
            template = template.replace("%csrf%", CSRF);
        }
        else {
            template = template.replace("%csrf%", "");
        }
        return template;
    }

    private CharSequence createScopes(Map<String, Object> model, HttpServletRequest request) {
        StringBuilder builder = new StringBuilder("
    "); @SuppressWarnings("unchecked") Map<String, String> scopes = (Map<String, String>) (model.containsKey("scopes") ? model.get("scopes") : request .getAttribute("scopes")); for (String scope : scopes.keySet()) { String approved = "true".equals(scopes.get(scope)) ? " checked" : ""; String denied = !"true".equals(scopes.get(scope)) ? " checked" : ""; String value = SCOPE.replace("%scope%", scope).replace("%key%", scope).replace("%approved%", approved) .replace("%denied%", denied); builder.append(value); } builder.append("
"
); return builder.toString(); } private static String CSRF = ""; private static String DENIAL = "
%csrf%
"
; private static String TEMPLATE = "

OAuth Approval

" + "

Do you authorize '${authorizationRequest.clientId}' to access your protected resources?

"
+ "
%csrf%%scopes%
"
+ "%denial%
"
; private static String SCOPE = "
  • %scope%: + " value='true'%approved%>Approve Deny
  • "
    ; }
    package com.zcw.sso.server;
    
    import org.springframework.context.expression.MapAccessor;
    import org.springframework.expression.Expression;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    import org.springframework.expression.spel.support.StandardEvaluationContext;
    import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
    import org.springframework.util.PropertyPlaceholderHelper;
    import org.springframework.web.servlet.View;
    import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @ClassName : SsoSpelView
     * @Description :
     * @Author : Zhaocunwei
     * @Date: 2020-07-04 16:54
     */
    public class SsoSpelView implements View {
        private final String template;
    
        private final String prefix;
    
        private final SpelExpressionParser parser = new SpelExpressionParser();
    
        private final StandardEvaluationContext context = new StandardEvaluationContext();
    
        private PropertyPlaceholderHelper.PlaceholderResolver resolver;
    
        public SsoSpelView(String template) {
            this.template = template;
            this.prefix = new RandomValueStringGenerator().generate() + "{";
            this.context.addPropertyAccessor(new MapAccessor());
            this.resolver = new PropertyPlaceholderHelper.PlaceholderResolver() {
                @Override
                public String resolvePlaceholder(String name) {
                    Expression expression = parser.parseExpression(name);
                    Object value = expression.getValue(context);
                    return value == null ? null : value.toString();
                }
            };
        }
    
        public SsoSpelView(String template, String prefix) {
            this.template = template;
            this.prefix = prefix;
        }
    
        @Override
        public String getContentType() {
            return "text/html";
        }
    
        @Override
        public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
                throws Exception {
            Map<String, Object> map = new HashMap<String, Object>(model);
            String path = ServletUriComponentsBuilder.fromContextPath(request).build()
                    .getPath();
            map.put("path", (Object) path==null ? "" : path);
            context.setRootObject(map);
            String maskedTemplate = template.replace("${", prefix);
            PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper(prefix, "}");
            String result = helper.replacePlaceholders(maskedTemplate, resolver);
            result = result.replace(prefix, "${");
            response.setContentType(getContentType());
            response.getWriter().append(result);
        }
    }
    
    
    

    你可能感兴趣的:(Spring,#,Spring)