日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

微服务接入oauth2_微服务权限终极解决方案,Spring Cloud Gateway+Oauth2实现统一认证和鉴权!...

發布時間:2023/12/20 javascript 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 微服务接入oauth2_微服务权限终极解决方案,Spring Cloud Gateway+Oauth2实现统一认证和鉴权!... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

最近發現了一個很好的微服務權限解決方案,可以通過認證服務進行統一認證,然后通過網關來統一校驗認證和鑒權。此方案為目前最新方案,僅支持Spring Boot 2.2.0、Spring Cloud Hoxton 以上版本,本文將詳細介紹該方案的實現,希望對大家有所幫助!

SpringCloud實戰電商項目mall-swarm(5.1k+star)地址:https://github.com/macrozheng/mall-swarm

前置知識

我們將采用Nacos作為注冊中心,Gateway作為網關,使用nimbus-jose-jwtJWT庫操作JWT令牌,對這些技術不了解的朋友可以看下下面的文章。
  • Spring Cloud Gateway:新一代API網關服務
  • Spring Cloud Alibaba:Nacos 作為注冊中心和配置中心使用
  • 聽說你的JWT庫用起來特別扭,推薦這款賊好用的!

應用架構

我們理想的解決方案應該是這樣的,認證服務負責認證,網關負責校驗認證和鑒權,其他API服務負責處理自己的業務邏輯。安全相關的邏輯只存在于認證服務和網關服務中,其他服務只是單純地提供服務而沒有任何安全相關邏輯。

相關服務劃分:

  • micro-oauth2-gateway:網關服務,負責請求轉發和鑒權功能,整合Spring Security+Oauth2;
  • micro-oauth2-auth:Oauth2認證服務,負責對登錄用戶進行認證,整合Spring Security+Oauth2;
  • micro-oauth2-api:受保護的API服務,用戶鑒權通過后可以訪問該服務,不整合Spring Security+Oauth2。

方案實現

下面介紹下這套解決方案的具體實現,依次搭建認證服務、網關服務和API服務。

micro-oauth2-auth

我們首先來搭建認證服務,它將作為Oauth2的認證服務使用,并且網關服務的鑒權功能也需要依賴它。
  • 在pom.xml中添加相關依賴,主要是Spring Security、Oauth2、JWT、Redis相關依賴;
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId></dependency><dependency><groupId>com.nimbusds</groupId><artifactId>nimbus-jose-jwt</artifactId><version>8.16</version></dependency><!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency> </dependencies>
  • 在application.yml中添加相關配置,主要是Nacos和Redis相關配置;
server:port: 9401 spring:profiles:active: devapplication:name: micro-oauth2-authcloud:nacos:discovery:server-addr: localhost:8848jackson:date-format: yyyy-MM-dd HH:mm:ssredis:database: 0port: 6379host: localhostpassword: management:endpoints:web:exposure:include: "*"
  • 使用keytool生成RSA證書jwt.jks,復制到resource目錄下,在JDK的bin目錄下使用如下命令即可;
keytool -genkey -alias jwt -keyalg RSA -keystore jwt.jks
  • 創建UserServiceImpl類實現Spring Security的UserDetailsService接口,用于加載用戶信息;
/*** 用戶管理業務類* Created by macro on 2020/6/19.*/ @Service public class UserServiceImpl implements UserDetailsService {private List<UserDTO> userList;@Autowiredprivate PasswordEncoder passwordEncoder;@PostConstructpublic void initData() {String password = passwordEncoder.encode("123456");userList = new ArrayList<>();userList.add(new UserDTO(1L,"macro", password,1, CollUtil.toList("ADMIN")));userList.add(new UserDTO(2L,"andy", password,1, CollUtil.toList("TEST")));}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {List<UserDTO> findUserList = userList.stream().filter(item -> item.getUsername().equals(username)).collect(Collectors.toList());if (CollUtil.isEmpty(findUserList)) {throw new UsernameNotFoundException(MessageConstant.USERNAME_PASSWORD_ERROR);}SecurityUser securityUser = new SecurityUser(findUserList.get(0));if (!securityUser.isEnabled()) {throw new DisabledException(MessageConstant.ACCOUNT_DISABLED);} else if (!securityUser.isAccountNonLocked()) {throw new LockedException(MessageConstant.ACCOUNT_LOCKED);} else if (!securityUser.isAccountNonExpired()) {throw new AccountExpiredException(MessageConstant.ACCOUNT_EXPIRED);} else if (!securityUser.isCredentialsNonExpired()) {throw new CredentialsExpiredException(MessageConstant.CREDENTIALS_EXPIRED);}return securityUser;}}
  • 添加認證服務相關配置Oauth2ServerConfig,需要配置加載用戶信息的服務UserServiceImpl及RSA的鑰匙對KeyPair;
/*** 認證服務器配置* Created by macro on 2020/6/19.*/ @AllArgsConstructor @Configuration @EnableAuthorizationServer public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {private final PasswordEncoder passwordEncoder;private final UserServiceImpl userDetailsService;private final AuthenticationManager authenticationManager;private final JwtTokenEnhancer jwtTokenEnhancer;@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory().withClient("client-app").secret(passwordEncoder.encode("123456")).scopes("all").authorizedGrantTypes("password", "refresh_token").accessTokenValiditySeconds(3600).refreshTokenValiditySeconds(86400);}@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {TokenEnhancerChain enhancerChain = new TokenEnhancerChain();List<TokenEnhancer> delegates = new ArrayList<>();delegates.add(jwtTokenEnhancer); delegates.add(accessTokenConverter());enhancerChain.setTokenEnhancers(delegates); //配置JWT的內容增強器endpoints.authenticationManager(authenticationManager).userDetailsService(userDetailsService) //配置加載用戶信息的服務.accessTokenConverter(accessTokenConverter()).tokenEnhancer(enhancerChain);}@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {security.allowFormAuthenticationForClients();}@Beanpublic JwtAccessTokenConverter accessTokenConverter() {JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();jwtAccessTokenConverter.setKeyPair(keyPair());return jwtAccessTokenConverter;}@Beanpublic KeyPair keyPair() {//從classpath下的證書中獲取秘鑰對KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());}}
  • 如果你想往JWT中添加自定義信息的話,比如說登錄用戶的ID,可以自己實現TokenEnhancer接口;
/*** JWT內容增強器* Created by macro on 2020/6/19.*/ @Component public class JwtTokenEnhancer implements TokenEnhancer {@Overridepublic OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();Map<String, Object> info = new HashMap<>();//把用戶ID設置到JWT中info.put("id", securityUser.getId());((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);return accessToken;} }
  • 由于我們的網關服務需要RSA的公鑰來驗證簽名是否合法,所以認證服務需要有個接口把公鑰暴露出來;
/*** 獲取RSA公鑰接口* Created by macro on 2020/6/19.*/ @RestController public class KeyPairController {@Autowiredprivate KeyPair keyPair;@GetMapping("/rsa/publicKey")public Map<String, Object> getKey() {RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();RSAKey key = new RSAKey.Builder(publicKey).build();return new JWKSet(key).toJSONObject();}}
  • 不要忘了還需要配置Spring Security,允許獲取公鑰接口的訪問;
/*** SpringSecurity配置* Created by macro on 2020/6/19.*/ @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll().antMatchers("/rsa/publicKey").permitAll().anyRequest().authenticated();}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}}
  • 創建一個資源服務ResourceServiceImpl,初始化的時候把資源與角色匹配關系緩存到Redis中,方便網關服務進行鑒權的時候獲取。
/*** 資源與角色匹配關系管理業務類* Created by macro on 2020/6/19.*/ @Service public class ResourceServiceImpl {private Map<String, List<String>> resourceRolesMap;@Autowiredprivate RedisTemplate<String,Object> redisTemplate;@PostConstructpublic void initData() {resourceRolesMap = new TreeMap<>();resourceRolesMap.put("/api/hello", CollUtil.toList("ADMIN"));resourceRolesMap.put("/api/user/currentUser", CollUtil.toList("ADMIN", "TEST"));redisTemplate.opsForHash().putAll(RedisConstant.RESOURCE_ROLES_MAP, resourceRolesMap);} }

micro-oauth2-gateway

接下來我們就可以搭建網關服務了,它將作為Oauth2的資源服務、客戶端服務使用,對訪問微服務的請求進行統一的校驗認證和鑒權操作。
  • 在pom.xml中添加相關依賴,主要是Gateway、Oauth2和JWT相關依賴;
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-resource-server</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-client</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-oauth2-jose</artifactId></dependency><dependency><groupId>com.nimbusds</groupId><artifactId>nimbus-jose-jwt</artifactId><version>8.16</version></dependency> </dependencies>
  • 在application.yml中添加相關配置,主要是路由規則的配置、Oauth2中RSA公鑰的配置及路由白名單的配置;
server:port: 9201 spring:profiles:active: devapplication:name: micro-oauth2-gatewaycloud:nacos:discovery:server-addr: localhost:8848gateway:routes: #配置路由規則- id: oauth2-api-routeuri: lb://micro-oauth2-apipredicates:- Path=/api/**filters:- StripPrefix=1- id: oauth2-auth-routeuri: lb://micro-oauth2-authpredicates:- Path=/auth/**filters:- StripPrefix=1discovery:locator:enabled: true #開啟從注冊中心動態創建路由的功能lower-case-service-id: true #使用小寫服務名,默認是大寫security:oauth2:resourceserver:jwt:jwk-set-uri: 'http://localhost:9401/rsa/publicKey' #配置RSA的公鑰訪問地址redis:database: 0port: 6379host: localhostpassword: secure:ignore:urls: #配置白名單路徑- "/actuator/**"- "/auth/oauth/token"
  • 對網關服務進行配置安全配置,由于Gateway使用的是WebFlux,所以需要使用@EnableWebFluxSecurity注解開啟;
/*** 資源服務器配置* Created by macro on 2020/6/19.*/ @AllArgsConstructor @Configuration @EnableWebFluxSecurity public class ResourceServerConfig {private final AuthorizationManager authorizationManager;private final IgnoreUrlsConfig ignoreUrlsConfig;private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;@Beanpublic SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());http.authorizeExchange().pathMatchers(ArrayUtil.toArray(ignoreUrlsConfig.getUrls(),String.class)).permitAll()//白名單配置.anyExchange().access(authorizationManager)//鑒權管理器配置.and().exceptionHandling().accessDeniedHandler(restfulAccessDeniedHandler)//處理未授權.authenticationEntryPoint(restAuthenticationEntryPoint)//處理未認證.and().csrf().disable();return http.build();}@Beanpublic Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX);jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstant.AUTHORITY_CLAIM_NAME);JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);}}
  • 在WebFluxSecurity中自定義鑒權操作需要實現ReactiveAuthorizationManager接口;
/*** 鑒權管理器,用于判斷是否有資源的訪問權限* Created by macro on 2020/6/19.*/ @Component public class AuthorizationManager 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 obj = redisTemplate.opsForHash().get(RedisConstant.RESOURCE_ROLES_MAP, uri.getPath());List<String> authorities = Convert.toList(String.class,obj);authorities = authorities.stream().map(i -> i = AuthConstant.AUTHORITY_PREFIX + i).collect(Collectors.toList());//認證通過且角色匹配的用戶可訪問當前路徑return mono.filter(Authentication::isAuthenticated).flatMapIterable(Authentication::getAuthorities).map(GrantedAuthority::getAuthority).any(authorities::contains).map(AuthorizationDecision::new).defaultIfEmpty(new AuthorizationDecision(false));}}
  • 這里我們還需要實現一個全局過濾器AuthGlobalFilter,當鑒權通過后將JWT令牌中的用戶信息解析出來,然后存入請求的Header中,這樣后續服務就不需要解析JWT令牌了,可以直接從請求的Header中獲取到用戶信息。
/*** 將登錄用戶的JWT轉化成用戶信息的全局過濾器* Created by macro on 2020/6/17.*/ @Component public class AuthGlobalFilter implements GlobalFilter, Ordered {private static Logger LOGGER = LoggerFactory.getLogger(AuthGlobalFilter.class);@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String token = exchange.getRequest().getHeaders().getFirst("Authorization");if (StrUtil.isEmpty(token)) {return chain.filter(exchange);}try {//從token中解析用戶信息并設置到Header中去String realToken = token.replace("Bearer ", "");JWSObject jwsObject = JWSObject.parse(realToken);String userStr = jwsObject.getPayload().toString();LOGGER.info("AuthGlobalFilter.filter() user:{}",userStr);ServerHttpRequest request = exchange.getRequest().mutate().header("user", userStr).build();exchange = exchange.mutate().request(request).build();} catch (ParseException e) {e.printStackTrace();}return chain.filter(exchange);}@Overridepublic int getOrder() {return 0;} }

micro-oauth2-api

最后我們搭建一個API服務,它不會集成和實現任何安全相關邏輯,全靠網關來保護它。
  • 在pom.xml中添加相關依賴,就添加了一個web依賴;
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency> </dependencies>
  • 在application.yml添加相關配置,很常規的配置;
server:port: 9501 spring:profiles:active: devapplication:name: micro-oauth2-apicloud:nacos:discovery:server-addr: localhost:8848 management:endpoints:web:exposure:include: "*"
  • 創建一個測試接口,網關驗證通過即可訪問;
/*** 測試接口* Created by macro on 2020/6/19.*/ @RestController public class HelloController {@GetMapping("/hello")public String hello() {return "Hello World.";}}
  • 創建一個LoginUserHolder組件,用于從請求的Header中直接獲取登錄用戶信息;
/*** 獲取登錄用戶信息* Created by macro on 2020/6/17.*/ @Component public class LoginUserHolder {public UserDTO getCurrentUser(){//從Header中獲取用戶信息ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = servletRequestAttributes.getRequest();String userStr = request.getHeader("user");JSONObject userJsonObject = new JSONObject(userStr);UserDTO userDTO = new UserDTO();userDTO.setUsername(userJsonObject.getStr("user_name"));userDTO.setId(Convert.toLong(userJsonObject.get("id")));userDTO.setRoles(Convert.toList(String.class,userJsonObject.get("authorities")));return userDTO;} }
  • 創建一個獲取當前用戶信息的接口。
/*** 獲取登錄用戶信息接口* Created by macro on 2020/6/19.*/ @RestController @RequestMapping("/user") public class UserController{@Autowiredprivate LoginUserHolder loginUserHolder;@GetMapping("/currentUser")public UserDTO currentUser() {return loginUserHolder.getCurrentUser();}}

功能演示

接下來我們來演示下微服務系統中的統一認證鑒權功能,所有請求均通過網關訪問。
  • 在此之前先啟動我們的Nacos和Redis服務,然后依次啟動micro-oauth2-auth、micro-oauth2-gateway及micro-oauth2-api服務;

  • 使用密碼模式獲取JWT令牌,訪問地址:http://localhost:9201/auth/oauth/token

  • 使用獲取到的JWT令牌訪問需要權限的接口,訪問地址:http://localhost:9201/api/hello

  • 使用獲取到的JWT令牌訪問獲取當前登錄用戶信息的接口,訪問地址:http://localhost:9201/api/user/currentUser

  • 當JWT令牌過期時,使用refresh_token獲取新的JWT令牌,訪問地址:http://localhost:9201/auth/oauth/token

  • 使用沒有訪問權限的andy賬號登錄,訪問接口時會返回如下信息,訪問地址:http://localhost:9201/api/hello

項目源碼地址

https://github.com/macrozheng/springcloud-learning/tree/master/micro-oauth2

總結

以上是生活随笔為你收集整理的微服务接入oauth2_微服务权限终极解决方案,Spring Cloud Gateway+Oauth2实现统一认证和鉴权!...的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。