spring cloud 微服務框架搭建(個人筆記)

父工程包引入

<properties>
       <spring-cloud-dependencies.version>Hoxton.SR3</spring-cloud-dependencies.version>
       <nacos.version>2.2.1.RELEASE</nacos.version>
       <dubbo.version>2.7.5</dubbo.version>
</properties>

<dependencyManagement>
        <dependencies>
        	<!--spring-cloud 依賴管理-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud-dependencies.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--nacos-->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
                <version>${nacos.version}</version>
            </dependency>
            <!--dubbo start-->
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-spring-boot-starter</artifactId>
                <version>${dubbo.version}</version>
            </dependency>
            <!--dubbo end-->
        </dependencies>
</dependencyManagement>

naocs註冊中心

本人使用的是nacos 作爲註冊中心
nacos是阿里開源的。具有可視化頁面。

子項目引包

<dependencies>
    <!--nacos-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>

配置文件

spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

duboo

配置

dubbo:
  application:
    name: ${spring.application.name}
  provider:
    timeout: 5000
  consumer:
    check: false #不檢查服務是否啓動
    timeout: 5000
  registry:
    address: nacos://${spring.cloud.nacos.discovery.server-addr}
    check: false #不檢查註冊表
  protocol:
    name: dubbo
    port: 20881
  scan:
    base-packages: com.*.*.service

zuul 網關

超時問題太煩了,感覺和spring 框架就不匹配。難受。本人已經放棄了

怎麼解決跨域問題呢,因爲zuul 是基於servlet的。直接在WebMvcConfig解決就行了

@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
	/* 解決跨域問題 */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 設置允許跨域的路徑
        registry.addMapping("/**")
                //放行哪些原始域
                .allowedOrigins("*")
                //是否發送Cookie信息
                .allowCredentials(true)
                //放行哪些原始域(請求方式)
                .allowedMethods("*")
                //放行哪些原始域(頭部信息)
                .allowedHeaders("*")
                // 跨域允許時間
                .maxAge(3600);
    }
}

配置

zuul:
  # host配置適用於routes 爲url請求服務的路由方式,如果是service-id路由方式則配置ribbon
  host:
    connect-timeout-millis: 6000
    socket-timeout-millis: 6000
    max-per-route-connections: 2000
    max-total-connections: 10000
ribbon:
  #請求處理的超時時間 下級服務響應最大時間,超出時間消費方(路由也是消費方)返回timeout
  ReadTimeout: 5000
  #ribbon請求連接的超時時間- 限制3秒內必須請求到服務,並不限制服務處理的返回時間
  ConnectTimeout: 3000
#設置hystrix超時(Edgware版本下default配置不生效,默認超時2,建議hystrix超時時間>其他超時時間配置[如ribbon])
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 5000

自定義啓動類配置

/**
 * @email [email protected]
 * @author:劉俊秦
 * @date: 2019/5/16 0016
 * @time: 上午 8:51
 * @remarks:自定義配置類
 */
@Component
@EnableDiscoveryClient
@EnableZuulProxy
public class CustomConfiguration {


}

Gateway 網關

由於是Webflux 與web框架衝突。好用

配置

spring:
  cloud:
    gateway:
      globalcors:
      	#跨域解決方案
        globalcors:
        corsConfigurations:
          '[/**]':
            #這裏有個allowCredentials: true這個東西是設置允許訪問攜帶cookie的,這點一定要和前端對應!
            allowCredentials: true
            #可以填寫多個域名用","隔開 例如:"http://www.xiaolc.cn,https://spring.io"  "*"代表允許所有
            allowedOrigins: "*"
            allowedMethods: "*"
            allowedHeaders: "*"
     routes:
     - id: gateway

如何優雅的擴展oauth2的認證方式。

本人蔘考了大量的文章。後覺得使用改變原aouth2框架的情況下,擴展我們需要的圖片驗證碼登陸。短信登陸之類的認證方式
在原創作者的代碼上進行了微量的修改,使代碼更加簡潔易懂。認證流程也不會被調用兩次或多次重複的情況。
(這裏的代碼不是全部的,我只將核心的貼出來,僅供諸君參考)

導包

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.60</version>
</dependency>

WebSecurityConfig 配置如下

package com.ljq.oauth2.config.basic;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

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

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

    //安全攔截機制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .formLogin()
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .anyRequest().authenticated()
        ;
    }

}

TokenConfig 的配置(作者使用jwt的token 不使用原有的短token)

package com.ljq.oauth2.config.basic;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;

@Configuration
public class TokenConfig {

    private static String signing_key = "liu_jun_qiin";

    /**
     * 功能描述:
     * 〈定義令牌〉
     *
     * @return : org.springframework.security.oauth2.provider.token.TokenStore
     * @author : ljq-劉俊秦
     * @date : 2020/6/6 0006 下午 2:30
     */
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        var converter = new JwtAccessTokenConverter();
        converter.setSigningKey(signing_key);
        return converter;
    }

}

AuthorizationServer oauth2認證的配置

package com.ljq.oauth2.config.basic;

import com.ljq.oauth2.filter.IntegrationAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.annotation.Resource;
import java.util.Arrays;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {


    @Resource
    private TokenStore tokenStore;

    @Resource
    private JwtAccessTokenConverter accessTokenConverter;

    @Resource
    private ClientDetailsService clientDetailsService;

    @Resource
    private AuthorizationCodeServices authorizationCodeServices;

    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private IntegrationAuthenticationFilter integrationAuthenticationFilter;

	/**
     * 功能描述:
     * 〈去除調用兩次自定義過濾連〉
     *  由於integrationAuthenticationFilter註解了@Component 註冊了一遍
     *  下一又加入了一次過濾連,所有需要這個bean 重置一下
     * @param integrationAuthenticationFilter 1
     * @return : org.springframework.boot.web.servlet.FilterRegistrationBean<?>
     * @author : ljq-劉俊秦
     * @date : 2020/6/10 0010 下午 2:39
     */
    @Bean
    public FilterRegistrationBean<?> registration(IntegrationAuthenticationFilter integrationAuthenticationFilter) {
        var registration = new FilterRegistrationBean<>(integrationAuthenticationFilter);
        registration.setEnabled(false);
        return registration;
    }


    /**
     * 功能描述:
     * 〈配置token 服務〉
     *
     * @return : org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices
     * @author : ljq-劉俊秦
     * @date : 2020/6/6 0006 下午 9:46
     */
    @Bean
    public AuthorizationServerTokenServices tokenService() {
        var service = new DefaultTokenServices();
        service.setClientDetailsService(clientDetailsService);
        service.setSupportRefreshToken(true);
        service.setTokenStore(tokenStore);
        //配置jwtToken
        var tokenEnhancer = new TokenEnhancerChain();
        tokenEnhancer.setTokenEnhancers(Arrays.asList(accessTokenConverter));
        service.setTokenEnhancer(tokenEnhancer);
        service.setAccessTokenValiditySeconds(7200); //令牌默認有效期2小時
        service.setRefreshTokenValiditySeconds(7200); //刷新令牌默認時間有效期3天
        return service;
    }

    /**
     * 功能描述:
     * 〈設置授權碼模式的授權碼如何存取〉
     *
     * @return : org.springframework.security.oauth2.provider.code.AuthorizationCodeServices
     * @author : ljq-劉俊秦
     * @date : 2020/6/6 0006 下午 9:46
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        //設置授權碼模式的授權碼如何存取,暫時採用內存方式
        return new InMemoryAuthorizationCodeServices();
    }

    /**
     * 用來配置客戶端詳情服務(ClientDetailsService),客戶端詳情信息在
     * 這裏進行初始化,你能夠把客戶端詳情信息寫死在這裏或者是通過數據庫來存儲調取詳情信息。
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                .inMemory()
                .withClient("c1")
                .secret(passwordEncoder.encode("liu_jun_qin"))
                .redirectUris("res1")
                .authorizedGrantTypes(
                        "authorization_code",
                        "password",
                        "client_credentials",
                        "implicit",
                        "refresh_token"
                )
                .scopes("all")
                .autoApprove(false)
                .redirectUris("http://www.baidu.com")
        ;
    }

    /**
     * 用來配置令牌(token)的訪問端點和令牌服務(token services)
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .tokenServices(tokenService())
                .authenticationManager(authenticationManager)
                .authorizationCodeServices(authorizationCodeServices)
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
        ;
    }

    /**
     * 用來配置令牌端點的安全約束
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients()
                .tokenKeyAccess("isAuthenticated()")
                .checkTokenAccess("permitAll()")
                .addTokenEndpointAuthenticationFilter(integrationAuthenticationFilter)
        ;
    }


}

首先了解一下 權限認證的過濾連。在我們需要的地方插入自定義的過濾攔截

IntegrationAuthenticationFilter 配置集成認證,用來分發我們的過濾器-例如:圖片驗證碼登陸、短信登陸這些

package com.ljq.oauth2.filter;

import com.alibaba.fastjson.JSONObject;
import com.ljq.oauth2.config.authenticationIntegrate.IntegrationAuthentication;
import com.ljq.oauth2.config.authenticationIntegrate.IntegrationAuthenticationContext;
import com.ljq.oauth2.config.authenticationIntegrate.IntegrationAuthenticator;
import com.ljq.oauth2.exception.BaseException;
import com.ljq.oauth2.util.ServiceResult;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

@Component
public class IntegrationAuthenticationFilter extends GenericFilterBean implements ApplicationContextAware {

    private static final String AUTH_TYPE_PARM_NAME = "auth_type";

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

    private Collection<IntegrationAuthenticator> authenticators;

    private ApplicationContext applicationContext;

    private final RequestMatcher requestMatcher;


    public IntegrationAuthenticationFilter() {
        this.requestMatcher = new OrRequestMatcher(
                new AntPathRequestMatcher(OAUTH_TOKEN_URL, "GET"),
                new AntPathRequestMatcher(OAUTH_TOKEN_URL, "POST")
        );
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest request = null;
        HttpServletResponse response = null;
        try {
            request = (HttpServletRequest) servletRequest;
            response = (HttpServletResponse) servletResponse;

            if (requestMatcher.matches(request)) {
                //設置integrate登錄信息
                IntegrationAuthentication integrationAuthentication = new IntegrationAuthentication();
                integrationAuthentication.setAuthType(request.getParameter(AUTH_TYPE_PARM_NAME));
                integrationAuthentication.setAuthParameters(request.getParameterMap());
                IntegrationAuthenticationContext.set(integrationAuthentication);
                try {
                    //預處理
                    this.prepare(integrationAuthentication);
                    
					filterChain.doFilter(request, response);
					
                    //後置處理
                    this.complete(integrationAuthentication);
                } finally {
                    IntegrationAuthenticationContext.clear();
                }
            }
            filterChain.doFilter(request, response);
        } catch (BaseException e) {
            writePrint(response, ServiceResult.failure(e.getCode(), e.getMessage()));
        }
    }

    /**
     * 進行預處理
     *
     * @param integrationAuthentication
     */
    private void prepare(IntegrationAuthentication integrationAuthentication) {

        //延遲加載認證器
        if (this.authenticators == null) {
            synchronized (this) {
                Map<String, IntegrationAuthenticator> integrationAuthenticatorMap = applicationContext.getBeansOfType(IntegrationAuthenticator.class);
                if (integrationAuthenticatorMap != null) {
                    this.authenticators = integrationAuthenticatorMap.values();
                }
            }
        }

        if (this.authenticators == null) {
            this.authenticators = new ArrayList<>();
        }

        for (IntegrationAuthenticator authenticator : authenticators) {
            if (authenticator.support(integrationAuthentication)) {
                authenticator.prepare(integrationAuthentication);
            }
        }
    }

    /**
     * 後置處理
     *
     * @param integrationAuthentication
     */
    private void complete(IntegrationAuthentication integrationAuthentication) {
        for (IntegrationAuthenticator authenticator : authenticators) {
            if (authenticator.support(integrationAuthentication)) {
                authenticator.complete(integrationAuthentication);
            }
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }


    public void writePrint(HttpServletResponse response, ServiceResult serviceResult) throws IOException {
        response.setContentType("application/json; charset=utf-8");
        response.setCharacterEncoding("UTF-8");
        var printWriter = response.getWriter();
        printWriter.write(JSONObject.toJSONString(serviceResult));
    }

}

IntegrationAuthentication 收集登陸認證信息

package com.ljq.oauth2.config.authenticationIntegrate;

import lombok.Data;

import java.util.Map;

/**
 * 功能描述:
 * 〈收集登陸認證信息〉
 *
 * @author : ljq-劉俊秦
 * @date : 2020/6/6 0006 下午 5:41
 */
@Data
public class IntegrationAuthentication {

    private String authType;
    private String username;
    private Map<String, String[]> authParameters;

    public String getAuthParameter(String paramter) {
        String[] values = this.authParameters.get(paramter);
        if (values != null && values.length > 0) {
            return values[0];
        }
        return null;
    }

}

IntegrationAuthenticator 認證處理配置接口

package com.ljq.oauth2.config.authenticationIntegrate;

import com.ljq.oauth2.pojo.vo.AuthUserVo;

/**
 * 功能描述:
 * 〈認證處理配置接口〉
 *
 * @author : ljq-劉俊秦
 * @date : 2020/6/8 0008 下午 3:48
 */
public interface IntegrationAuthenticator {

    /**
     * 處理集成認證
     * @param integrationAuthentication
     * @return
     */
    AuthUserVo authenticate(IntegrationAuthentication integrationAuthentication);


    /**
     * 進行預處理
     * @param integrationAuthentication
     */
    void prepare(IntegrationAuthentication integrationAuthentication);

     /**
     * 判斷是否支持集成認證類型
     * @param integrationAuthentication
     * @return
     */
    boolean support(IntegrationAuthentication integrationAuthentication);

    /** 認證結束後執行
     * @param integrationAuthentication
     */
    void complete(IntegrationAuthentication integrationAuthentication);

}

IntegrationAuthenticationContext 將認證信息儲存在上下文中,集成身份驗證上下文

package com.ljq.oauth2.config.authenticationIntegrate;

/**
 * 功能描述:
 * 〈將認證信息儲存在上下文中,集成身份驗證上下文〉
 *
 * @author : ljq-劉俊秦
 * @date : 2020/6/6 0006 下午 5:42
 */
public class IntegrationAuthenticationContext {

    private static ThreadLocal<IntegrationAuthentication> holder = new ThreadLocal<>();

    public static void set(IntegrationAuthentication integrationAuthentication){
        holder.set(integrationAuthentication);
    }

    public static IntegrationAuthentication get(){
        return holder.get();
    }

    public static void clear(){
        holder.remove();
    }
}

AbstractPreparableIntegrationAuthenticator 認證處理配置 實現成抽象類-可單獨處理特殊數據

package com.ljq.oauth2.config.authenticationIntegrate;

import com.ljq.oauth2.pojo.vo.AuthUserVo;

/**
 * 功能描述:
 * 〈認證處理配置 實現成抽象類-可單獨處理特殊數據〉
 *
 * @author : ljq-劉俊秦
 * @date : 2020/6/8 0008 下午 3:50
 */
public abstract class AbstractPreparableIntegrationAuthenticator implements IntegrationAuthenticator {

    @Override
    public abstract AuthUserVo authenticate(IntegrationAuthentication integrationAuthentication);

    @Override
    public abstract void prepare(IntegrationAuthentication integrationAuthentication);

    @Override
    public abstract boolean support(IntegrationAuthentication integrationAuthentication);

    @Override
    public void complete(IntegrationAuthentication integrationAuthentication) {

    }

}

默認登錄處理 UsernamePasswordAuthenticator

/**
 * 默認登錄處理
 *
 * @author LIQIU
 * @date 2018-3-31
 **/
@Component
@Primary
public class UsernamePasswordAuthenticator extends AbstractPreparableIntegrationAuthenticator {

    @Resource
    private IUserService userService;

    @Override
    public AuthUserVo authenticate(IntegrationAuthentication integrationAuthentication) {
        String username = integrationAuthentication.getUsername();
        var user = userService.getByUserName(Long.parseLong(username));
        var authUserVo = new AuthUserVo();
        BeanUtils.copyProperties(user, authUserVo);
        return  authUserVo;
    }

    @Override
    public void prepare(IntegrationAuthentication integrationAuthentication) {

    }

    @Override
    public boolean support(IntegrationAuthentication integrationAuthentication) {
        return StringUtils.isEmpty(integrationAuthentication.getAuthType());
    }

    @Override
    public void complete(IntegrationAuthentication integrationAuthentication) {
        super.complete(integrationAuthentication);
    }
}

關鍵地方
本在在這寫了一個假的驗證,不貼項目裏邊的redis 驗證
僅供參考

圖片驗證碼處理器

/**
 * 功能描述:
 * 〈圖片驗證碼處理器〉
 *
 * @author : ljq-劉俊秦
 * @date : 2020/6/6 0006 下午 7:21
 */
@Component
public class VerificationCodeIntegrationAuthenticator extends UsernamePasswordAuthenticator {

    private final static String VERIFICATION_CODE_AUTH_TYPE = "vc";

    @Resource
    private IUserService userService;

    @Override
    public AuthUserVo authenticate(IntegrationAuthentication integrationAuthentication) {
        //獲取用戶名,實際值是手機號
        String username = integrationAuthentication.getUsername();
        var user = userService.getByUserName(Long.parseLong(username));
        var authUserVo = new AuthUserVo();
        BeanUtils.copyProperties(user, authUserVo);
        return  authUserVo;
    }

    @Override
    public void prepare(IntegrationAuthentication integrationAuthentication) {
        String vcToken = integrationAuthentication.getAuthParameter("vc_token");
        String vcCode = integrationAuthentication.getAuthParameter("vc_code");

        //驗證驗證碼
        var yzm = RedisUtils.get(vcToken);
        if (yzm == null) {
            throw new BaseException(ReturnCode.VERIFICATION_NOT_EXISTENCE);
        }
        if (!yzm.equalsIgnoreCase(vcCode)) {
            throw new BaseException(ReturnCode.CAPTCHA);
        }

    }

    @Override
    public boolean support(IntegrationAuthentication integrationAuthentication) {
        return VERIFICATION_CODE_AUTH_TYPE.equals(integrationAuthentication.getAuthType());
    }

    @Override
    public void complete(IntegrationAuthentication integrationAuthentication) {
        //驗證完成後刪除驗證碼
        String vcToken = integrationAuthentication.getAuthParameter("vc_token");
        RedisUtils.delete(vcToken);
    }
}

短信登陸驗證處理器

/**
 * 功能描述:
 * 〈短信登陸驗證處理器〉
 *
 * @author : ljq-劉俊秦
 * @date : 2020/6/8 0008 下午 4:07
 */

@Component
public class SmsIntegrationAuthenticator extends AbstractPreparableIntegrationAuthenticator {

    @Resource
    private IUserService userService;
    @Resource
    private PasswordEncoder passwordEncoder;

    public final static String SMS_AUTH_TYPE = "sms";

    @Override
    public AuthUserVo authenticate(IntegrationAuthentication integrationAuthentication) {
        //獲取密碼,實際值是驗證碼
        String password = integrationAuthentication.getAuthParameter("password");
        //獲取用戶名,實際值是手機號
        String username = integrationAuthentication.getUsername();
        var user = userService.getByUserName(Long.parseLong(username));
        var authUserVo = new AuthUserVo();
        BeanUtils.copyProperties(user, authUserVo);
        authUserVo.setPassword(passwordEncoder.encode(password));
        return authUserVo;
    }

    @Override
    public void prepare(IntegrationAuthentication integrationAuthentication) {
        String smsToken = integrationAuthentication.getAuthParameter("sms_token");
        String smsCode = integrationAuthentication.getAuthParameter("password");

        //驗證驗證碼
        var yzm = RedisUtils.get(smsToken);
        if (yzm == null) {
            throw new BaseException(ReturnCode.VERIFICATION_NOT_EXISTENCE);
        }
        if (!yzm.equalsIgnoreCase(smsCode)) {
            throw new BaseException(ReturnCode.CAPTCHA);
        }

    }

    @Override
    public boolean support(IntegrationAuthentication integrationAuthentication) {
        return SMS_AUTH_TYPE.equals(integrationAuthentication.getAuthType());
    }

    @Override
    public void complete(IntegrationAuthentication integrationAuthentication) {
        //手機登陸完成刪除驗證碼
        String smsToken = integrationAuthentication.getAuthParameter("sms_token");
        RedisUtils.delete(smsToken);
    }

}

**實現 UserDetailsService **

@Service("userDetailsService")
public class AuthUserImpl implements UserDetailsService {

    @Resource
    private IUserService userService;

    private List<IntegrationAuthenticator> authenticators;

    @Autowired(required = false)
    public void setIntegrationAuthenticators(List<IntegrationAuthenticator> authenticators) {
        this.authenticators = authenticators;
    }

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {

        IntegrationAuthentication integrationAuthentication = IntegrationAuthenticationContext.get();
        //判斷是否是集成登錄
        if (integrationAuthentication == null) {
            integrationAuthentication = new IntegrationAuthentication();
        }
        integrationAuthentication.setUsername(userName);
        var authUserVo = this.authenticate(integrationAuthentication);

        if(authUserVo == null){
            throw new BaseException(ReturnCode.PHONE_NO_REGISTERED);
        }

        setAuthorize(authUserVo);
        return authUserVo;
    }

    /**
     * 設置授權信息
     */
    public UserDetails setAuthorize(AuthUserVo authUserVo) {
        //真實情況,這裏需要查詢用戶信息。進行組裝
        var authorities = userService.getAuthorizes(authUserVo.getId());
        authUserVo.setAuthorities(mapToGrantedAuthorities(authorities));
        return authUserVo;
    }

    /**
     * 功能描述:
     * 〈將用戶的權限封裝〉
     *
     * @param resources 1
     * @return : java.util.List<org.springframework.security.core.GrantedAuthority>
     * @author : ljq-劉俊秦
     * @date : 2020/6/6 0006 下午 6:26
     */
    private static List<GrantedAuthority> mapToGrantedAuthorities(List<String> resources) {
        return resources.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }

    private AuthUserVo authenticate(IntegrationAuthentication integrationAuthentication) {
        if (this.authenticators != null) {
            for (IntegrationAuthenticator authenticator : authenticators) {
                if (authenticator.support(integrationAuthentication)) {
                    return authenticator.authenticate(integrationAuthentication);
                }
            }
        }
        return null;
    }

}

ouath2 如何自定義返回自己定義的格式數據,而不是 走DefaultWebResponseExceptionTranslator

看源碼知道,只需要實現WebResponseExceptionTranslator類的translate 方法即可
所有自定義一個異常處理就行了

*定義一個MyWebResponseExceptionTranslator *

@Component
public class MyWebResponseExceptionTranslator implements WebResponseExceptionTranslator {

    @Override
    public ResponseEntity<?> translate(Exception e) throws Exception {
        if (e instanceof OAuth2Exception) {
            return ResponseEntity.ok(ServiceResult.failure(((OAuth2Exception) e).getHttpErrorCode() + "", e.getMessage()));
        }
        throw e;
    }

}

配置一下 AuthorizationServer(AuthorizationServerConfigurerAdapter)中的配置

@Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
               .exceptionTranslator(myWebResponseExceptionTranslator)
        ;
    }

如何添加自定義的 jwt token擴展數據

自定義一個CustomerEnhancer

package com.ronrun.auth.config.securityOath2.token;

import com.alibaba.fastjson.JSON;
import com.ronrun.auth.pojo.vo.AuthUserVo;
import com.ronrun.common.constant.AuthConstant;
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 org.springframework.stereotype.Component;

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

@Component
public class CustomerEnhancer implements TokenEnhancer {

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
        final Map<String, Object> additionalInfo = new HashMap<>();
        var user = (AuthUserVo) oAuth2Authentication.getUserAuthentication().getPrincipal();
        //用戶名jwt 默然就已經加入所以無需在添加
        additionalInfo.put(AuthConstant.authUserVo, JSON.toJSONString(user));
        ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(additionalInfo);
        return oAuth2AccessToken;
    }

}

認證服務 中配置

@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
	@Resource
    private CustomerEnhancer customerEnhancer;

	/**
     * 功能描述:
     * 〈配置token 服務〉
     *
     * @return : org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices
     * @author : ljq-劉俊秦
     * @date : 2020/6/6 0006 下午 9:46
     */
    @Bean
    public AuthorizationServerTokenServices tokenService() {
        var service = new DefaultTokenServices();
        service.setClientDetailsService(clientDetailsService);
        service.setSupportRefreshToken(true);
        service.setTokenStore(tokenStore);
        //配置jwtToken
        var tokenEnhancer = new TokenEnhancerChain();
        tokenEnhancer.setTokenEnhancers(Arrays.asList(customerEnhancer,accessTokenConverter));
        service.setTokenEnhancer(tokenEnhancer);
        service.setAccessTokenValiditySeconds(7200); //令牌默認有效期2小時
        service.setRefreshTokenValiditySeconds(86400); //刷新令牌默認時間有效期24小時
        return service;
    }

總結

想要擴展什麼登陸類型可以直接實現AbstractPreparableIntegrationAuthenticator類來處理,自己需要認證的數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章