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