springSecurity + OAuth2 獲取Token流程分析以及增加協議授權模式


springSecurity + OAuth2 獲取Token流程分析以及增加協議授權模式

 

 

一、什么是OAuth2協議?

OAuth 2.0 是一個關於授權的開放的網絡協議,是目前最流行的授權機制。

數據的所有者告訴系統,同意授權第三方應用進入系統,獲取這些數據。系統從而產生一個短期的進入令牌(token),用來代替密碼,供第三方應用使用。

由於授權的場景眾多,OAuth 2.0 協議定義了獲取令牌的四種授權方式,分別是:

  • 授權碼模式:授權碼模式(authorization code)是功能最完整、流程最嚴密的授權模式。它的特點就是通過客戶端的后台服務器,與"服務提供商"的認證服務器進行互動。
  • 簡化模式:簡化模式(implicit grant type)不通過第三方應用程序的服務器,直接在瀏覽器中向認證服務器申請令牌,跳過了"授權碼"這個步驟,因此得名。所有步驟在瀏覽器中完成,令牌對訪問者是可見的,且客戶端不需要認證。
  • 密碼模式:密碼模式(Resource Owner Password Credentials Grant)中,用戶向客戶端提供自己的用戶名和密碼。客戶端使用這些信息,向"服務商提供商"索要授權。
  • 客戶端模式:客戶端模式(Client Credentials Grant)指客戶端以自己的名義,而不是以用戶的名義,向"服務提供商"進行認證。嚴格地說,客戶端模式並不屬於OAuth框架所要解決的問題。在這種模式中,用戶直接向客戶端注冊,客戶端以自己的名義要求"服務提供商"提供服務,其實不存在授權問題。

四種授權模式分別使用不同的 grant_type 來區分

二、為什么要自定義授權類型?

雖然 OAuth2 協議定義了4種標准的授權模式,但是在實際開發過程中還是遠遠滿足不了各種變態的業務場景,需要我們去擴展。

例如增加圖形驗證碼、手機驗證碼、手機號密碼登錄等等的場景

而常見的做法都是通過增加 過濾器Filter 的方式來擴展 Spring Security 授權,但是這樣的實現方式有兩個問題:

  1. 脫離了 OAuth2 的管理
  2. 不靈活:例如系統使用 密碼模式 授權,網頁版需要增加圖形驗證碼校驗,但是手機端APP又不需要的情況下,使用增加 Filter 的方式去實現就比較麻煩了。

所以目前在 Spring Security 中比較優雅和靈活的擴展方式就是通過自定義 grant_type 來增加授權模式。

三、實現思路

在擴展之前首先需要先了解 Spring Security 的整個授權流程,我以 密碼模式 為例去展開分析,如下圖所示

 

3.1. 流程分析

整個授權流程關鍵點分為以下兩個部分:

我們先看一下獲取token的運行流程:

(1)在發起 URL+/oauth/token 獲取token的請求后,實際上是請求 TokenEndpoint 類的postAccessToken或者getAccessToken方法,就相當於一個普通的concoller請求方法,根據請求類型是get或者post,其實get請求內部也是調用post請求的方法)

 

 (2)在postAccessToken這個方法中,在這個方法的132行調用TokenGranter類的grant方法來獲取token,這個方法也是最重要的,通過這個方法我們可以對請求的參數進行校驗是否合法,是否給予令牌。

 

(3)TokenGranter是一個接口,它有多個實現類,CompositeTokenGranter是其中之一,在grant方法中,會循環遍歷所有的授權方式,根據請求參數攜帶的授權方式碼,來匹配對應的授權處理實現類,調用實現類中的grant方法。那么關鍵點來了,請求參數中攜帶的是我們自定義的授權方式碼,如果要匹配上,那么首先我們要創建自定義的授權處理類,然后把這個授權處理類放入Spring Security默認的授權處理集合中,這樣才能循環匹配上,進行下一步。 

4)創建自定義授權處理類,我們可以繼承TokenGranter來實現自定義的身份驗證以便獲取token,而AbstractTokenGranter是一個繼承TokenGranter的實現類,一般我們都會繼承這個類進行使用。

從下面代碼可以看出,這個抽象類的grant方法返回token,在最后調用了getOAuth2Authentication方法,所以

我們只需要繼承AbstractTokenGranter類然后重寫getOAuth2Authentication方法就可以了。

(5)對於參數的校驗,在上圖中36行代碼,authenticationManager.authenticate(userAuth);這個方法中進行校驗的,下面我們先看一下源碼,再去實現自定義的認證提供商類AuthenticationProvider

        在源碼中,AuthenticationProvider是一個接口,里面有兩個方法,一個是校驗參數的方法,另一個則是根據當前認證信息匹配出對應的認證提供商類,這個接口有很多實現類,其中ProviderManager類是非常關鍵的,在這個類中的參數校驗方法中,會根據當前要認證的對象,獲取符合要求的所有的認證提供商,然后循環匹配出對應的認證提供商,在調取校驗方法進行參數校驗

 

 

 (6)由上圖可知,我們只需要實現ProviderManager接口的兩個方法,自定義自己的參數校驗方法,並且把這個自定義的ProviderManager加入到認證提供商集合中,在循環匹配的時候即可匹配到我們自定義的ProviderManager,進行參數校驗。

 

 (7)把自定義的ProviderManager放入ProviderManager集合中,我的方法如下,在配置文件中,重寫configure方法,配置ProviderManager,這里除了配置我們自定義的ProviderManager之外,還需要額外配置默認的密碼授權模式的ProviderManager,否則client認證將不會通過。

 

 

 

 (8)經過以上步驟,已經實現自定義token的獲取,最后要做的是把這個自定義授權模式類,放入系統默認的授權模式集合中,這樣在CompositeTokenGrantergrant方法中,才能循環匹配到我們自定義的授權模式,進而直接獲取token。我們先來看一下,系統默認的授權模式集合是在哪里初始化的?答案在AuthorizationServerEndpointsConfigurer這個類中559行,調用了getDefaultTokenGranters()方法,並且創建了 CompositeTokenGranter的實例對象,進行初始化。查看源碼可以發現,系統已經把默認的授權模式全都寫死在程序里了,因此我的解決思路是如下的

AuthorizationServerEndpointsConfigurer中,初始化默認授權方式的代碼復制一下,在配置文件中額外重新配置自定義的模式,代碼部分截圖(完整參考最后)如下

 

 

 

 (9)授權認證服務端點配置

 

 

 

 (10)最后:查看源碼可以發現,當前clientid擁有的授權方式碼是通過ClientDetails client = clientDetailsService.loadClientByClientId(clientId);獲取的,在數據庫中配置的,因此我們需要再oauth_client_details表中,在對應的 clientid 的  authorized_grant_types 字段中加上自定義的授權模式碼。

 

第一部分:關於授權類型 grant_type 的解析

  1. 每種 grant_type 都會有一個對應的 TokenGranter 實現類。
  2. 所有 TokenGranter 實現類都通過 CompositeTokenGranter 中的 tokenGranters 集合存起來。
  3. 然后通過判斷 grantType 參數來定位具體使用那個 TokenGranter 實現類來處理授權。

第二部分:關於授權登錄邏輯

  1. 每種 授權方式 都會有一個對應的 AuthenticationProvider 實現類來實現。
  2. 所有 AuthenticationProvider 實現類都通過 ProviderManager 中的 providers 集合存起來。
  3. TokenGranter 類會 new 一個 AuthenticationToken 實現類,如 UsernamePasswordAuthenticationToken 傳給 ProviderManager 類。
  4. ProviderManager 則通過 AuthenticationToken 來判斷具體使用那個 AuthenticationProvider 實現類來處理授權。
  5. 具體的登錄邏輯由 AuthenticationProvider 實現類來實現,如 DaoAuthenticationProvider

 

完整代碼:

OpenIdTokenGranter
package com.lpw.CustomerSecurity;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @Author dw
 * @ClassName OpenIdTokenGranter
 * @Description
 * @Date 2020/9/15 13:19
 * @Version 1.0
 */
public class OpenIdTokenGranter extends AbstractTokenGranter {
    private static final String GRANT_TYPE = "openId";

    private final AuthenticationManager authenticationManager;

    public OpenIdTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices
            , ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
        super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
        this.authenticationManager = authenticationManager;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());
        Authentication userAuth = new OpenIdAuthenticationToken(parameters);
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
        userAuth = authenticationManager.authenticate(userAuth);
        if (userAuth == null || !userAuth.isAuthenticated()) {
            throw new InvalidGrantException("Could not authenticate openId: " + parameters);
        }

        OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, userAuth);
    }

}
package com.lpw.CustomerSecurity;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @Author dw
 * @ClassName TokenGranterConfig
 * @Description token授權模式配置類
 * @Date 2020/9/15 13:26
 * @Version 1.0
 */
@Configuration
public class TokenGranterConfig {

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private OpenIdUserDetailService userDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private TokenStore tokenStore;

    private AuthorizationCodeServices authorizationCodeServices;

    private boolean reuseRefreshToken = true;

    private AuthorizationServerTokenServices tokenServices;

    private TokenGranter tokenGranter;

    /**
     * 授權模式
     *
     * @return
     */
    @Bean
    public TokenGranter tokenGranter() {
        if (tokenGranter == null) {
            tokenGranter = new TokenGranter() {
                private CompositeTokenGranter delegate;

                @Override
                public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
                    if (delegate == null) {
                        delegate = new CompositeTokenGranter(getDefaultTokenGranters());
                    }
                    return delegate.grant(grantType, tokenRequest);
                }
            };
        }
        return tokenGranter;
    }

    /**
     * 程序支持的授權類型
     *
     * @return
     */
    private List<TokenGranter> getDefaultTokenGranters() {
        AuthorizationServerTokenServices tokenServices = tokenServices();
        AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
        OAuth2RequestFactory requestFactory = requestFactory();

        List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
        // 添加授權碼模式
        tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailsService, requestFactory));
        // 添加刷新令牌的模式
        tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetailsService, requestFactory));
        // 添加隱式授權模式
        tokenGranters.add(new ImplicitTokenGranter(tokenServices, clientDetailsService, requestFactory));
        // 添加客戶端模式
        tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory));
        // 添加自定義Openid授權模式
        tokenGranters.add(new OpenIdTokenGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
        if (authenticationManager != null) {
            // 添加密碼模式
            tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
        }
        return tokenGranters;
    }

    /**
     * TokenServices
     *
     * @return
     */
    private AuthorizationServerTokenServices tokenServices() {
        if (tokenServices != null) {
            return tokenServices;
        }
        this.tokenServices = createDefaultTokenServices();
        return tokenServices;
    }

    /**
     * 授權碼API
     *
     * @return
     */
    private AuthorizationCodeServices authorizationCodeServices() {
        if (authorizationCodeServices == null) {
            authorizationCodeServices = new InMemoryAuthorizationCodeServices();
        }
        return authorizationCodeServices;
    }

    /**
     * OAuth2RequestFactory的默認實現,它初始化參數映射中的字段,
     * 驗證授權類型(grant_type)和范圍(scope),並使用客戶端的默認值填充范圍(scope)(如果缺少這些值)。
     *
     * @return
     */
    private OAuth2RequestFactory requestFactory() {
        return new DefaultOAuth2RequestFactory(clientDetailsService);
    }

    /**
     * 默認 TokenService
     *
     * @return
     */
    private DefaultTokenServices createDefaultTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore);
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setReuseRefreshToken(reuseRefreshToken);
        tokenServices.setClientDetailsService(clientDetailsService);
        addUserDetailsService(tokenServices, this.userDetailsService);
        return tokenServices;
    }

    /**
     * 添加預身份驗證
     *
     * @param tokenServices
     * @param userDetailsService
     */
    private void addUserDetailsService(DefaultTokenServices tokenServices, UserDetailsService userDetailsService) {
        if (userDetailsService != null) {
            PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
            provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(userDetailsService));
            tokenServices.setAuthenticationManager(new ProviderManager(Arrays.<AuthenticationProvider>asList(provider)));
        }
    }
}
package com.lpw.CustomerSecurity;

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

import java.util.Collection;

/**
 * @Author dw
 * @ClassName OpenIdAuthenticationToken
 * @Description
 * @Date 2020/9/15 13:09
 * @Version 1.0
 */
public class OpenIdAuthenticationToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    private final Object principal;

    public OpenIdAuthenticationToken(Object principal) {
        super(null);
        this.principal = principal;
        setAuthenticated(false);
    }

    public OpenIdAuthenticationToken(Object principal,
                                     Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        super.setAuthenticated(true);
    }


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

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

    @Override
    public void setAuthenticated(boolean isAuthenticated) {
        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();
    }
}
package com.lpw.CustomerSecurity;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Map;

/**
 * @Author dw
 * @ClassName OpenIdAuthenticationProvider
 * @Description
 * @Date 2020/9/15 13:14
 * @Version 1.0
 */
public class OpenIdAuthenticationProvider implements AuthenticationProvider {

    private CustomUserDetailsService customUserDetailsService;


    @Override
    public Authentication authenticate(Authentication authentication) {
        OpenIdAuthenticationToken authenticationToken = (OpenIdAuthenticationToken) authentication;
        Map<String, String> principal = (Map<String, String>) authenticationToken.getPrincipal();
        UserDetails userDetails = customUserDetailsService.loadUserByUsername(principal);
        if (userDetails == null) {
            throw new InternalAuthenticationServiceException("openId錯誤");
        }
        OpenIdAuthenticationToken authenticationResult = new OpenIdAuthenticationToken(userDetails, userDetails.getAuthorities());
        // 設置前端傳過來的具體參數
        authenticationResult.setDetails(authenticationToken.getDetails());
        return authenticationResult;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return OpenIdAuthenticationToken.class.isAssignableFrom(authentication);
    }

    public CustomUserDetailsService getCustomUserDetailsService() {
        return customUserDetailsService;
    }

    public void setCustomUserDetailsService(CustomUserDetailsService customUserDetailsService) {
        this.customUserDetailsService = customUserDetailsService;
    }




}
package com.lpw.CustomerSecurity;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lpw.Enum.WeChatUserParamEnum;
import com.lpw.dao.*;
import com.lpw.entity.*;
import com.lpw.utils.DateUtil;
import com.lpw.utils.WeChatUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Map;
import java.util.Objects;

/**
 * @Author dw
 * @ClassName OpenIdUserDetailService
 * @Description 微信小程序授權,注冊,獲取用戶信息
 * @Date 2020/9/15 17:03
 * @Version 1.0
 */
@Service
public class OpenIdUserDetailService implements CustomUserDetailsService {

    @Autowired
    private IExhibitionDao exhibitionDao;
    @Autowired
    private IUserMapperDao userMapperDao;
    @Autowired
    private IVisitorExhibitionDao visitorExhibitionDao;
    @Autowired
    private IVisitorDao visitorDao;
    @Autowired
    private IVisitorUserDao visitorUserDao;


    @Override
    public UserDetails loadUserByUsername(String userName, String userType) throws UsernameNotFoundException {
        return null;
    }

    @Override
    public UserDetails loadUserByUsername(Map<String, String> principal) throws UsernameNotFoundException {
        // 獲取微信用戶授權的信息
        User weChatUserInfo = getWeChatUserInfo(principal);
        String exhibitionId = principal.get(WeChatUserParamEnum.EXHIBITION_ID.getValue());
        // 檢查用戶信息
        userIsRegistered(weChatUserInfo, exhibitionId);
        // 查詢用戶
        VisitorUser visitorUser = visitorUserDao.queryVisitorUser(weChatUserInfo.getLoginName());
        return visitorUser;
    }

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        return null;
    }

    /**
     * 獲取微信用戶信息
     */
    public User getWeChatUserInfo(Map<String, String> principal) {
        String code = principal.get(WeChatUserParamEnum.CODE.getValue());
        String exhibitionId = principal.get(WeChatUserParamEnum.EXHIBITION_ID.getValue());
        String rawData = principal.get(WeChatUserParamEnum.RAW_DATA.getValue());
//        String encrypteData = principal.get(WeChatUserParamEnum.ENCRYPTE_DATA.getValue());
//        String iv = principal.get(WeChatUserParamEnum.IV.getValue());
        String signature = principal.get(WeChatUserParamEnum.SIGNATURE.getValue());
        // 查詢當前小程序的配置信息
        Exhibition exhibition = exhibitionDao.queryExhibitionWechatConf(Integer.valueOf(exhibitionId));
        // 2.開發者服務器 登錄憑證校驗接口 appId + appSecret + 接收小程序發送的code
        WeChatUtil weChatUtil = new WeChatUtil(exhibition);
        JSONObject SessionKeyOpenId = weChatUtil.getSessionKeyOrOpenId(code);
        // 3.接收微信接口服務 獲取返回的參數
        String openid = SessionKeyOpenId.get("openid", String.class);
        String sessionKey = SessionKeyOpenId.get("session_key", String.class);
        // 用戶非敏感信息:rawData
        // 簽名:signature
        JSONObject rawDataJson = JSONUtil.parseObj(rawData);
        // 4.校驗簽名 小程序發送的簽名signature與服務器端生成的簽名signature2 = sha1(rawData + sessionKey)
        String signature2 = DigestUtils.sha1Hex(rawData + sessionKey);
        if (!signature.equals(signature2)) {
            throw new UsernameNotFoundException("簽名不匹配");
        }
        //encrypteData比rowData多了appid和openid
//        JSONObject userInfo = weChatUtil.getUserInfo(encrypteData,
//                sessionKey, iv);
        User user = new User();
        user.setLoginName(openid);
        user.setName(rawDataJson.get("nickName", String.class));
        user.setIcon(rawDataJson.get("avatarUrl", String.class));
//        0:女      1: 男
        Integer sex = rawDataJson.get("gender", String.class).equals("女") ? 0 : 1;
        user.setSex(sex);
        return user;
    }

    /**
     * 查詢當前訪客是否買需要注冊
     */
    @Transactional(rollbackForClassName = "RuntimeException")
    public void userIsRegistered(User weChatUserInfo, String exhibitionId){
        // 查詢當前用戶是否存在
        MyUser myUser = userMapperDao.loadUserByUsername(weChatUserInfo.getLoginName());
        Integer userId = null;
        if (Objects.isNull(myUser)) {
            // 新增用戶
            weChatUserInfo.setCreateTime(DateUtil.currentDateTime());
             userMapperDao.insert(weChatUserInfo);
            userId = weChatUserInfo.getUserId();
        }else {
            userId = myUser.getUserId();
        }
        // 查詢當前訪客是否存在
        QueryWrapper<Visitor> visitorQueryWrapper = new QueryWrapper<>();
        visitorQueryWrapper.lambda().eq(Visitor::getUserId, userId);
        Visitor hasVisitor = visitorDao.selectOne(visitorQueryWrapper);
        Integer visitorId = null;
        if(Objects.isNull(hasVisitor)){
            // 插入訪客到訪客表
            Visitor visitor = new Visitor();
            visitor.setUserId(userId);
            visitor.setCreateTime(DateUtil.currentDateTime());
            visitorDao.insert(visitor);
            visitorId = visitor.getVisitorId();
        }else {
            visitorId = hasVisitor.getVisitorId();
        }
        // 查詢當前訪客是否注冊到了當前展會
        QueryWrapper<VisitorExhibition> query = new QueryWrapper<>();
        query.lambda().eq(VisitorExhibition::getExhibitionId, exhibitionId);
        query.lambda().eq(VisitorExhibition::getVisitorId, visitorId);
        VisitorExhibition visitorExhibition = visitorExhibitionDao.selectOne(query);
        if (Objects.isNull(visitorExhibition)) {
            // 當前訪客未注冊當前展會,新增訪客、展會關系
            VisitorExhibition visitorExhibition1 = new VisitorExhibition();
            visitorExhibition1.setExhibitionId(Integer.valueOf(exhibitionId));
            visitorExhibition1.setVisitorId(visitorId);
            visitorExhibition1.setOpenId(weChatUserInfo.getLoginName());
            visitorExhibitionDao.insert(visitorExhibition1);
        }
    }




}
package com.lpw.CustomerSecurity;


import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.Map;

/**
 * @Author dw
 * @ClassName CustomUserDetailsService
 * @Description 繼承原來的UserDetailsService新增自定義方法
 * @Date 2020/8/19 17:23
 * @Version 1.0
 */
public interface CustomUserDetailsService extends UserDetailsService {

    /**
     * 根據用戶名、用戶類型查詢用戶
     * @param userName  用戶名
     * @param userType 用戶類型
     * @return
     * @throws UsernameNotFoundException
     */
    UserDetails loadUserByUsername(String userName, String userType) throws UsernameNotFoundException;


    /**
     * 根據OpenId、 展會id查詢用戶
     * @param principal 前端傳過來的微信小程序授權登錄的參數信息
     * @return
     * @throws UsernameNotFoundException
     */
    UserDetails loadUserByUsername(Map<String, String> principal) throws UsernameNotFoundException;



}
package com.lpw.auth;

import com.lpw.CustomerSecurity.UserDetailsServiceImpl;
import com.lpw.security.CustomWebResponseExceptionTranslator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.ClientDetailsService;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;

/**
 * @Author dw
 * @ClassName AuthorizationServerConfig
 * @Description
 * @Date 2020/8/17 10:46
 * @Version 1.0
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {


    @Autowired
    private DataSource dataSource;
    /**
     *  認證管理器,認證用戶信息
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 用戶認證處理邏輯
     */
    @Autowired
    private UserDetailsServiceImpl myUserDetailService;

    /**
     * 認證異常全局處理
     */
    @Autowired
    private CustomWebResponseExceptionTranslator webResponseExceptionTranslator;


    @Autowired
    private TokenGranter tokenGranter;


    @Bean
    public TokenStore tokenStore() {
        //使用內存中的 token store
//        return new InMemoryTokenStore();
        //使用Jdbctoken store
        return new JdbcTokenStore(dataSource);
    }

    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }



    /**
     * @return
     * @Author dw
     * @Description 配置token的過期日期等
     * @Date 2020/4/23 18:32
     * @Param
     */
    @Bean
    @Primary
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        // access token有效期
        services.setAccessTokenValiditySeconds(60 * 60 * 24);
        // refresh token有效期
        services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
        // 支持使用refresh token刷新access token
        services.setSupportRefreshToken(true);
        // 允許重復使用refresh token
        services.setReuseRefreshToken(true);
        services.setTokenStore(tokenStore());
        return services;
    }


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 配置客戶端, 用於client認證
        clients.withClientDetails(clientDetails());
// 第一次使用的時候,需要配置客戶端信息,或者手動添加客戶端信息到數據庫oauth_client_details表中
//        clients.jdbc(dataSource)
//                .withClient("myClient")
//                .secret(new BCryptPasswordEncoder().encode("123456"))
//                .authorizedGrantTypes("password", "refresh_token")//允許授權范圍
////                .authorities("ROLE_ADMIN","ROLE_USER")//客戶端可以使用的權限
//                .scopes("all")
//                .accessTokenValiditySeconds(7200)
//                .refreshTokenValiditySeconds(7200);
    }


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore())
                .tokenServices(defaultTokenServices())
                .authenticationManager(authenticationManager)
                .userDetailsService(myUserDetailService)
                .reuseRefreshTokens(true)
                //接收GET和POST
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                //四種授權模式+刷新令牌的模式+自定義授權模式
                .tokenGranter(tokenGranter)
                .exceptionTranslator(webResponseExceptionTranslator);

    }



    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                //允許表單登錄
                .allowFormAuthenticationForClients();
    }


    public static void main(String[] args) {
        System.out.println(new BCryptPasswordEncoder().encode("123456"));
    }


}
package com.lpw.security;

import com.lpw.CustomerSecurity.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @Author dw
 * @ClassName WebSecurityConfig
 * @Description
 * @Date 2020/8/19 13:53
 * @Version 1.0
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private OpenIdUserDetailService openIdUserDetailService;

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean(name = "CustomerAuthenticationProvider")
    public AuthenticationProvider customAuthenticationProvider() {
        CustomerAuthenticationProvider customAuthenticationProvider = new CustomerAuthenticationProvider();
        customAuthenticationProvider.setUserDetailsService(userDetailsService);
        customAuthenticationProvider.setHideUserNotFoundExceptions(false);
        customAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        return customAuthenticationProvider;
    }

    @Bean(name = "openIdAuthenticationProvider")
    public AuthenticationProvider openIdAuthenticationProvider() {
        OpenIdAuthenticationProvider customAuthenticationProvider = new OpenIdAuthenticationProvider();
        customAuthenticationProvider.setCustomUserDetailsService(openIdUserDetailService);
        return customAuthenticationProvider;
    }

    @Bean(name = "daoAuthenticationProvider")
    public AuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        return daoAuthenticationProvider;
    }


    /**
     * 自定義的用戶認證
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(customAuthenticationProvider())
                .authenticationProvider(daoAuthenticationProvider())
                .authenticationProvider(openIdAuthenticationProvider());
    }


    /**
     * 用來配置攔截保護的請求
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //定義哪些url需要被保護  哪些不需要保護
                .authorizeRequests()
                //定義這兩個鏈接不需要登錄可訪問
                .antMatchers("/oauth/token", "oauth/check_token", "/v2/api-docs", "/swagger-resources/configuration/ui",
                        "/swagger-resources", "/swagger-resources/configuration/security",
                        "/swagger-ui.html", "/course/coursebase/**", "/webjars/**").permitAll()
                //定義所有的都不需要登錄  目前是測試需要
//             .antMatchers("/**").permitAll()
                .anyRequest().authenticated() //其他的都需要登錄
                //.antMatchers("/sys/**").hasRole("admin")///sys/**下的請求 需要有admin的角色
                .and()
                .formLogin()
//                .loginPage("/login") //如果未登錄則跳轉登錄的頁面   這兒可以控制登錄成功和登錄失敗跳轉的頁面
                .and()
                .csrf().disable();//防止跨站請求  spring security中默認開啟
    }

}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM