如何把做的網(wǎng)站變成鏈接如何網(wǎng)站關(guān)鍵詞優(yōu)化
微服務(wù)分布式認(rèn)證授權(quán)方案
在分布式授權(quán)系統(tǒng)中,授權(quán)服務(wù)要獨(dú)立成一個(gè)模塊做統(tǒng)一授權(quán),無論客戶端是瀏覽器,app或者第三方,都會(huì)在授權(quán)服務(wù)中獲取權(quán)限,并通過網(wǎng)關(guān)訪問資源
OAuth2的四種授權(quán)模式
授權(quán)碼模式
授權(quán)服務(wù)器將授權(quán)碼(AuthorizationCode)轉(zhuǎn)經(jīng)瀏覽器發(fā)送給client,客戶端拿著授權(quán)碼向授權(quán)服務(wù)器索要訪問access_token,這種模式是四種模式中最安全的一種模式。一般用于Web服務(wù)器端應(yīng)用或第三方的原生App調(diào)用資源服務(wù)的時(shí)候
密碼模式
資源擁有者將用戶名、密碼發(fā)送給客戶端,客戶端拿著資源擁有者的用戶名、密碼向授權(quán)服務(wù)器請(qǐng)求令牌(access_token),密碼模式使用較多,適應(yīng)于第一方的單頁面應(yīng)用以及第一方的原生App
客戶端模式
客戶端向授權(quán)服務(wù)器發(fā)送自己的身份信息,并請(qǐng)求令牌(access_token),確認(rèn)客戶端身份無誤后,將令牌(access_token)發(fā)送給client,這種模式是最方便但最不安全的模式。因此這就要求我們對(duì)client完全的信任,而client本身也是安全的。因此這種模式一般用來提供給我們完全信任的服務(wù)器端服務(wù)
簡(jiǎn)化模式
資源擁有者打開客戶端,客戶端要求資源擁有者給予授權(quán),它將瀏覽器被重定向到授權(quán)服務(wù)器,授權(quán)服務(wù)器將授權(quán)碼將令牌(access_token)以Hash的形式存放在重定向uri的fargment中發(fā)送給瀏覽器
?spring security 部分請(qǐng)看:spring security 認(rèn)證授權(quán)
OAuth2的配置
- 添加配置類,添加@EnableAuthorizationServer 注解標(biāo)注這是一個(gè)認(rèn)證中心
- 繼承?AuthorizationServerConfigurerAdapter
@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {/*** 令牌存儲(chǔ)策略*/@Autowiredprivate TokenStore tokenStore;/*** 客戶端存儲(chǔ)策略,這里使用內(nèi)存方式,后續(xù)可以存儲(chǔ)在數(shù)據(jù)庫*/@Autowiredprivate ClientDetailsService clientDetailsService;/*** Security的認(rèn)證管理器,密碼模式需要用到*/@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate JwtAccessTokenConverter jwtAccessTokenConverter;@Autowiredprivate OAuthServerAuthenticationEntryPoint authenticationEntryPoint;@Autowiredprivate DataSource dataSource;/*** 配置客戶端詳情,并不是所有的客戶端都能接入授權(quán)服務(wù)*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {//TODO 暫定內(nèi)存模式,后續(xù)可以存儲(chǔ)在數(shù)據(jù)庫中,更加方便clients.inMemory()//客戶端id.withClient("myjszl")//客戶端秘鑰.secret(new BCryptPasswordEncoder().encode("123"))//資源id,唯一,比如訂單服務(wù)作為一個(gè)資源,可以設(shè)置多個(gè).resourceIds("res1")//授權(quán)模式,總共四種,1. authorization_code(授權(quán)碼模式)、password(密碼模式)、client_credentials(客戶端模式)、implicit(簡(jiǎn)化模式)//refresh_token并不是授權(quán)模式,.authorizedGrantTypes("authorization_code","password","client_credentials","implicit","refresh_token")//允許的授權(quán)范圍,客戶端的權(quán)限,這里的all只是一種標(biāo)識(shí),可以自定義,為了后續(xù)的資源服務(wù)進(jìn)行權(quán)限控制.scopes("all")//false 則跳轉(zhuǎn)到授權(quán)頁面.autoApprove(false)//授權(quán)碼模式的回調(diào)地址.redirectUris("http://www.baidu.com");}/*** 令牌管理服務(wù)的配置*/@Beanpublic AuthorizationServerTokenServices tokenServices() {DefaultTokenServices services = new DefaultTokenServices();//客戶端端配置策略services.setClientDetailsService(clientDetailsService);//支持令牌的刷新services.setSupportRefreshToken(true);//令牌服務(wù)services.setTokenStore(tokenStore);//access_token的過期時(shí)間services.setAccessTokenValiditySeconds(60 * 60 * 24 * 3);//refresh_token的過期時(shí)間services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);//設(shè)置令牌增強(qiáng),使用JwtAccessTokenConverter進(jìn)行轉(zhuǎn)換services.setTokenEnhancer(jwtAccessTokenConverter);return services;}/*** 授權(quán)碼模式的service,使用授權(quán)碼模式authorization_code必須注入*/@Beanpublic AuthorizationCodeServices authorizationCodeServices() {return new JdbcAuthorizationCodeServices(dataSource);//todo 授權(quán)碼暫時(shí)存在內(nèi)存中,后續(xù)可以存儲(chǔ)在數(shù)據(jù)庫中
// return new InMemoryAuthorizationCodeServices();}/*** 配置令牌訪問的端點(diǎn)*/@Override@SuppressWarnings("ALL")public void configure(AuthorizationServerEndpointsConfigurer endpoints) {//將自定義的授權(quán)類型添加到tokenGranters中List<TokenGranter> tokenGranters = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter()));tokenGranters.add(new MobilePwdGranter(authenticationManager, tokenServices(), clientDetailsService,new DefaultOAuth2RequestFactory(clientDetailsService)));endpoints//設(shè)置異常WebResponseExceptionTranslator,用于處理用戶名,密碼錯(cuò)誤、授權(quán)類型不正確的異常.exceptionTranslator(new OAuthServerWebResponseExceptionTranslator())//授權(quán)碼模式所需要的authorizationCodeServices.authorizationCodeServices(authorizationCodeServices())//密碼模式所需要的authenticationManager.authenticationManager(authenticationManager)//令牌管理服務(wù),無論哪種模式都需要.tokenServices(tokenServices())//添加進(jìn)入tokenGranter.tokenGranter(new CompositeTokenGranter(tokenGranters))//只允許POST提交訪問令牌,uri:/oauth/token.allowedTokenEndpointRequestMethods(HttpMethod.POST);}/*** 配置令牌訪問的安全約束*/@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) {//自定義ClientCredentialsTokenEndpointFilter,用于處理客戶端id,密碼錯(cuò)誤的異常OAuthServerClientCredentialsTokenEndpointFilter endpointFilter = new OAuthServerClientCredentialsTokenEndpointFilter(security,authenticationEntryPoint);endpointFilter.afterPropertiesSet();security.addTokenEndpointAuthenticationFilter(endpointFilter);security.authenticationEntryPoint(authenticationEntryPoint)//開啟/oauth/token_key驗(yàn)證端口權(quán)限訪問.tokenKeyAccess("permitAll()")//開啟/oauth/check_token驗(yàn)證端口認(rèn)證權(quán)限訪問.checkTokenAccess("permitAll()");//一定不要添加allowFormAuthenticationForClients,否則自定義的OAuthServerClientCredentialsTokenEndpointFilter不生效
// .allowFormAuthenticationForClients();}
}
主要配置點(diǎn):
- 先配置客戶端詳細(xì)信息ClientDetailService? ?configure(ClientDetailsServiceConfigurer clients)
- 在配置令牌訪問端點(diǎn)(需要先配置令牌管理服務(wù)和tokenStore表示令牌的存放和管理策略)configure(AuthorizationServerEndpointsConfigurer endpoints)? 配置加入令牌管理服務(wù)和其他oauth2.0需要的管理器和service
- 最后配置訪問令牌的約束條件,configure(AuthorizationServerSecurityConfigurer security)定義哪些端點(diǎn)可以訪問?
spring security的配置?
- 定義配置類,添加@EnableWebSecurity 注解
- 繼承WebSecurityConfigurerAdapter
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate SmsCodeSecurityConfig smsCodeSecurityConfig;/*** 加密算法*/@BeanPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http//注入自定義的授權(quán)配置類.apply(smsCodeSecurityConfig).and().authorizeRequests()//注銷的接口需要放行.antMatchers("/oauth/logout").permitAll().anyRequest().authenticated().and().formLogin().loginProcessingUrl("/login").permitAll().and().csrf().disable();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//從數(shù)據(jù)庫中查詢用戶信息auth.userDetailsService(userDetailsService);}/*** AuthenticationManager對(duì)象在OAuth2認(rèn)證服務(wù)中要使用,提前放入IOC容器中* Oauth的密碼模式需要*/@Override@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}
}
配置內(nèi)容:
- 定義加密算法,定義查詢用戶信息方案,在oauth2中密碼模式會(huì)用到授權(quán)管理器
- 定義訪問策略,哪些放行,哪些需要授權(quán)?
JWT令牌配置?
不透明令牌則是令牌本身不存儲(chǔ)任何信息,比如一串UUID,InMemoryTokenStore就類似這種
因此資源服務(wù)拿到這個(gè)令牌必須調(diào)調(diào)用認(rèn)證授權(quán)服務(wù)的接口進(jìn)行令牌的校驗(yàn),高并發(fā)的情況下延遲很高,性能很低
透明令牌本身就存儲(chǔ)這部分用戶信息,比如JWT,資源服務(wù)可以調(diào)用自身的服務(wù)對(duì)該令牌進(jìn)行校驗(yàn)解析,不必調(diào)用認(rèn)證服務(wù)的接口去校驗(yàn)令牌
JwtAccessTokenConverter令牌增強(qiáng)類,用于JWT令牌和OAuth身份進(jìn)行轉(zhuǎn)換,內(nèi)部生成JWT令牌,封裝進(jìn)入OAuth2AccessToken對(duì)象返回
在gateway中也需要有jwt令牌和tokenstore配置,代碼相同
/*** * 令牌的配置*/
@Configuration
public class AccessTokenConfig {/*** 令牌的存儲(chǔ)策略*/@Beanpublic TokenStore tokenStore() {//使用JwtTokenStore生成JWT令牌return new JwtTokenStore(jwtAccessTokenConverter());}/*** JwtAccessTokenConverter* TokenEnhancer的子類,在JWT編碼的令牌值和OAuth身份驗(yàn)證信息之間進(jìn)行轉(zhuǎn)換。* TODO:后期可以使用非對(duì)稱加密*/@Beanpublic JwtAccessTokenConverter jwtAccessTokenConverter(){JwtAccessTokenConverter converter = new JwtAccessTokenEnhancer();// 設(shè)置秘鑰converter.setSigningKey(TokenConstant.SIGN_KEY);/** 設(shè)置自定義得的令牌轉(zhuǎn)換器,從map中轉(zhuǎn)換身份信息* fix(*):修復(fù)刷新令牌無法獲取用戶詳細(xì)信息的問題*/converter.setAccessTokenConverter(new JwtEnhanceAccessTokenConverter());return converter;}/*** JWT令牌增強(qiáng),繼承JwtAccessTokenConverter* 將業(yè)務(wù)所需的額外信息放入令牌中,這樣下游微服務(wù)就能解析令牌獲取*/public static class JwtAccessTokenEnhancer extends JwtAccessTokenConverter {/*** 重寫enhance方法,在其中擴(kuò)展*/@Overridepublic OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {Object principal = authentication.getUserAuthentication().getPrincipal();if (principal instanceof SecurityUser){//獲取userDetailService中查詢到用戶信息SecurityUser user=(SecurityUser)principal;//將額外的信息放入到LinkedHashMap中LinkedHashMap<String,Object> extendInformation=new LinkedHashMap<>();//設(shè)置用戶的userIdextendInformation.put(TokenConstant.USER_ID,user.getUserId());//添加到additionalInformation((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(extendInformation);}return super.enhance(accessToken, authentication);}}
}
gateway認(rèn)證管理器?
JwtAuthenticationManager,需要實(shí)現(xiàn)ReactiveAuthenticationManager這個(gè)接口。
認(rèn)證管理的作用就是獲取傳遞過來的令牌,對(duì)其進(jìn)行解析、驗(yàn)簽、過期時(shí)間判定?
/*** * JWT認(rèn)證管理器,主要的作用就是對(duì)攜帶過來的token進(jìn)行校驗(yàn),比如過期時(shí)間,加密方式等* 一旦token校驗(yàn)通過,則交給鑒權(quán)管理器進(jìn)行鑒權(quán)*/
@Component
@Slf4j
public class JwtAuthenticationManager implements ReactiveAuthenticationManager {/*** 使用JWT令牌進(jìn)行解析令牌*/@Autowiredprivate TokenStore tokenStore;@Overridepublic Mono<Authentication> authenticate(Authentication authentication) {return Mono.justOrEmpty(authentication).filter(a -> a instanceof BearerTokenAuthenticationToken).cast(BearerTokenAuthenticationToken.class).map(BearerTokenAuthenticationToken::getToken).flatMap((accessToken -> {OAuth2AccessToken oAuth2AccessToken = this.tokenStore.readAccessToken(accessToken);//根據(jù)access_token從數(shù)據(jù)庫獲取不到OAuth2AccessTokenif (oAuth2AccessToken == null) {return Mono.error(new InvalidTokenException("無效的token!"));} else if (oAuth2AccessToken.isExpired()) {return Mono.error(new InvalidTokenException("token已過期!"));}OAuth2Authentication oAuth2Authentication = this.tokenStore.readAuthentication(accessToken);if (oAuth2Authentication == null) {return Mono.error(new InvalidTokenException("無效的token!"));} else {return Mono.just(oAuth2Authentication);}})).cast(Authentication.class);}
}
gateway 鑒權(quán)管理器
經(jīng)過認(rèn)證管理器JwtAuthenticationManager認(rèn)證成功后,就需要對(duì)令牌進(jìn)行鑒權(quán),如果該令牌無訪問資源的權(quán)限,則不允通過
JwtAccessManager,實(shí)現(xiàn)ReactiveAuthorizationManager
/**** 鑒權(quán)管理器 用于認(rèn)證成功之后對(duì)用戶的權(quán)限進(jìn)行鑒權(quán)* TODO 此處的邏輯:從redis中獲取對(duì)應(yīng)的uri的權(quán)限,與當(dāng)前用戶的token的攜帶的權(quán)限進(jìn)行對(duì)比,如果包含則鑒權(quán)成功* 企業(yè)中可能有不同的處理邏輯,可以根據(jù)業(yè)務(wù)需求更改鑒權(quán)的邏輯**/
@Slf4j
@Component
@Deprecated
public class JwtAccessManager implements ReactiveAuthorizationManager<AuthorizationContext> {@Autowiredprivate RedisTemplate<String,Object> redisTemplate;@Overridepublic Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {//從Redis中獲取當(dāng)前路徑可訪問角色列表URI uri = authorizationContext.getExchange().getRequest().getURI();Object value = redisTemplate.opsForHash().get(SysConstant.OAUTH_URLS, uri.getPath());List<String> authorities = Convert.toList(String.class,value);//認(rèn)證通過且角色匹配的用戶可訪問當(dāng)前路徑return mono//判斷是否認(rèn)證成功.filter(Authentication::isAuthenticated)//獲取認(rèn)證后的全部權(quán)限.flatMapIterable(Authentication::getAuthorities).map(GrantedAuthority::getAuthority)//如果權(quán)限包含則判斷為true.any(authorities::contains).map(AuthorizationDecision::new).defaultIfEmpty(new AuthorizationDecision(false));}}
gateway security oauth2.0配置
- 添加@EnableWebFluxSecurity注解,由于spring cldou gateway使用的Flux,因此需要使用@EnableWebFluxSecurity注解開啟
- 配置SecurityWebFilterChain添加跨域認(rèn)證異常鑒權(quán)配置
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {/*** JWT的鑒權(quán)管理器*/@Autowiredprivate ReactiveAuthorizationManager<AuthorizationContext> accessManager;/*** token過期的異常處理*/@Autowiredprivate RequestAuthenticationEntryPoint requestAuthenticationEntryPoint;/*** 權(quán)限不足的異常處理*/@Autowiredprivate RequestAccessDeniedHandler requestAccessDeniedHandler;/*** 系統(tǒng)參數(shù)配置*/@Autowiredprivate SysParameterConfig sysConfig;/*** token校驗(yàn)管理器*/@Autowiredprivate ReactiveAuthenticationManager tokenAuthenticationManager;@Autowiredprivate CorsFilter corsFilter;@BeanSecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) throws Exception{//認(rèn)證過濾器,放入認(rèn)證管理器tokenAuthenticationManagerAuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(tokenAuthenticationManager);authenticationWebFilter.setServerAuthenticationConverter(new ServerBearerTokenAuthenticationConverter());http.httpBasic().disable().csrf().disable().authorizeExchange()//白名單直接放行.pathMatchers(ArrayUtil.toArray(sysConfig.getIgnoreUrls(),String.class)).permitAll()//其他的請(qǐng)求必須鑒權(quán),使用鑒權(quán)管理器.anyExchange().access(accessManager)//鑒權(quán)的異常處理,權(quán)限不足,token失效.and().exceptionHandling().authenticationEntryPoint(requestAuthenticationEntryPoint).accessDeniedHandler(requestAccessDeniedHandler).and()// 跨域過濾器.addFilterAt(corsFilter, SecurityWebFiltersOrder.CORS)//token的認(rèn)證過濾器,用于校驗(yàn)token和認(rèn)證.addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION);return http.build();}
}
gateway全局過濾器
校驗(yàn)token,并將通過認(rèn)證授權(quán)的請(qǐng)求轉(zhuǎn)發(fā)下層服務(wù),并將token信息放到請(qǐng)求頭中
/*** 1、白名單直接放行* 2、校驗(yàn)token* 3、讀取token中存放的用戶信息* 4、重新封裝用戶信息,加密成功json數(shù)據(jù)放入請(qǐng)求頭中傳遞給下游微服務(wù)*/
@Component
@Slf4j
public class GlobalAuthenticationFilter implements GlobalFilter, Ordered {/*** JWT令牌的服務(wù)*/@Autowiredprivate TokenStore tokenStore;@Autowiredprivate StringRedisTemplate stringRedisTemplate;/*** 系統(tǒng)參數(shù)配置*/@Autowiredprivate SysParameterConfig sysConfig;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String requestUrl = exchange.getRequest().getPath().value();//1、白名單放行,比如授權(quán)服務(wù)、靜態(tài)資源.....if (checkUrls(sysConfig.getIgnoreUrls(),requestUrl)){return chain.filter(exchange);}//2、 檢查token是否存在String token = getToken(exchange);if (StringUtils.isBlank(token)) {return invalidTokenMono(exchange);}//3 判斷是否是有效的tokenOAuth2AccessToken oAuth2AccessToken;try {//解析token,使用tokenStoreoAuth2AccessToken = tokenStore.readAccessToken(token);Map<String, Object> additionalInformation = oAuth2AccessToken.getAdditionalInformation();//令牌的唯一IDString jti=additionalInformation.get(TokenConstant.JTI).toString();/**查看黑名單中是否存在這個(gè)jti,如果存在則這個(gè)令牌不能用****/Boolean hasKey = stringRedisTemplate.hasKey(SysConstant.JTI_KEY_PREFIX + jti);if (hasKey)return invalidTokenMono(exchange);//取出用戶身份信息String user_name = additionalInformation.get("user_name").toString();//獲取用戶權(quán)限List<String> authorities = (List<String>) additionalInformation.get("authorities");//從additionalInformation取出userIdString userId = additionalInformation.get(TokenConstant.USER_ID).toString();JSONObject jsonObject=new JSONObject();jsonObject.put(TokenConstant.PRINCIPAL_NAME, user_name);jsonObject.put(TokenConstant.AUTHORITIES_NAME,authorities);//過期時(shí)間,單位秒jsonObject.put(TokenConstant.EXPR,oAuth2AccessToken.getExpiresIn());jsonObject.put(TokenConstant.JTI,jti);//封裝到JSON數(shù)據(jù)中jsonObject.put(TokenConstant.USER_ID, userId);//將解析后的token加密放入請(qǐng)求頭中,方便下游微服務(wù)解析獲取用戶信息String base64 = Base64.encode(jsonObject.toJSONString());//放入請(qǐng)求頭中ServerHttpRequest tokenRequest = exchange.getRequest().mutate().header(TokenConstant.TOKEN_NAME, base64).build();ServerWebExchange build = exchange.mutate().request(tokenRequest).build();return chain.filter(build);} catch (InvalidTokenException e) {//解析token異常,直接返回token無效return invalidTokenMono(exchange);}}@Overridepublic int getOrder() {return 0;}/*** 對(duì)url進(jìn)行校驗(yàn)匹配*/private boolean checkUrls(List<String> urls,String path){AntPathMatcher pathMatcher = new AntPathMatcher();for (String url : urls) {if (pathMatcher.match(url,path))return true;}return false;}/*** 從請(qǐng)求頭中獲取Token*/private String getToken(ServerWebExchange exchange) {String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");if (StringUtils.isBlank(tokenStr)) {return null;}String token = tokenStr.split(" ")[1];if (StringUtils.isBlank(token)) {return null;}return token;}/*** 無效的token*/private Mono<Void> invalidTokenMono(ServerWebExchange exchange) {return buildReturnMono(ResultMsg.builder().code(ResultCode.INVALID_TOKEN.getCode()).msg(ResultCode.INVALID_TOKEN.getMsg()).build(), exchange);}private Mono<Void> buildReturnMono(ResultMsg resultMsg, ServerWebExchange exchange) {ServerHttpResponse response = exchange.getResponse();byte[] bits = JSON.toJSONString(resultMsg).getBytes(StandardCharsets.UTF_8);DataBuffer buffer = response.bufferFactory().wrap(bits);response.setStatusCode(HttpStatus.UNAUTHORIZED);response.getHeaders().add("Content-Type", "application/json;charset:utf-8");return response.writeWith(Mono.just(buffer));}
}
本文參照項(xiàng)目:spring-security-oauth2?