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

本文介绍了如何利用Spring Cloud Gateway和Oauth2实现微服务的统一认证和鉴权解决方案。通过认证服务进行统一认证,网关负责校验认证和鉴权,其他API服务只需关注业务逻辑。详细步骤包括认证服务的搭建,如配置Spring Security、Oauth2、JWT,以及网关服务的配置,如JWT验证、权限管理。此外,还提供了API服务的简单示例。
摘要由CSDN通过智能技术生成

摘要

最近发现了一个很好的微服务权限解决方案,可以通过认证服务进行统一认证,然后通过网关来统一校验认证和鉴权。此方案为目前最新方案,仅支持Spring Boot 2.2.0、Spring Cloud Hoxton 以上版本,本文将详细介绍该方案的实现,希望对大家有所帮助!

前置知识我们将采用Nacos作为注册中心,Gateway作为网关,使用nimbus-jose-jwtJWT库操作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相关依赖;

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-security

org.springframework.cloud

spring-cloud-starter-oauth2

com.nimbusds

nimbus-jose-jwt

8.16

org.springframework.boot

spring-boot-starter-data-redis

在application.yml中添加相关配置,主要是Nacos和Redis相关配置;server:

port: 9401

spring:

profiles:

active: dev

application:

name: micro-oauth2-auth

cloud:

nacos:

discovery:

server-addr: localhost:8848

jackson:

date-format: yyyy-MM-dd HH:mm:ss

redis:

database: 0

port: 6379

host: localhost

password:

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 userList;

@Autowired

private PasswordEncoder passwordEncoder;

@PostConstruct

public 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")));

}

@Override

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

List 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;

@Override

public 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);

}

@Override

public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

TokenEnhancerChain enhancerChain = new TokenEnhancerChain();

List delegates = new ArrayList<>();

delegates.add(jwtTokenEnhancer);

delegates.add(accessTokenConverter());

enhancerChain.setTokenEnhancers(delegates); //配置JWT的内容增强器

endpoints.authenticationManager(authenticationManager)

.userDetailsService(userDetailsService) //配置加载用户信息的服务

.accessTokenConverter(accessTokenConverter())

.tokenEnhancer(enhancerChain);

}

@Override

public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {

security.allowFormAuthenticationForClients();

}

@Bean

public JwtAccessTokenConverter accessTokenConverter() {

JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();

jwtAccessTokenConverter.setKeyPair(keyPair());

return jwtAccessTokenConverter;

}

@Bean

public 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 {

@Override

public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {

SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();

Map 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 {

@Autowired

private KeyPair keyPair;

@GetMapping("/rsa/publicKey")

public Map 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 {

@Override

protected void configure(HttpSecurity http) throws Exception {

http.authorizeRequests()

.requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()

.antMatchers("/rsa/publicKey").permitAll()

.anyRequest().authenticated();

}

@Bean

@Override

public AuthenticationManager authenticationManagerBean() throws Exception {

return super.authenticationManagerBean();

}

@Bean

public PasswordEncoder passwordEncoder() {

return new BCryptPasswordEncoder();

}

}创建一个资源服务ResourceServiceImpl,初始化的时候把资源与角色匹配关系缓存到Redis中,方便网关服务进行鉴权的时候获取。/**

* 资源与角色匹配关系管理业务类

* Created by macro on 2020/6/19.

*/

@Service

public class ResourceServiceImpl {

private Map> resourceRolesMap;

@Autowired

private RedisTemplate redisTemplate;

@PostConstruct

public 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相关依赖;

org.springframework.boot

spring-boot-starter-webflux

org.springframework.cloud

spring-cloud-starter-gateway

org.springframework.security

spring-security-config

org.springframework.security

spring-security-oauth2-resource-server

org.springframework.security

spring-security-oauth2-client

org.springframework.security

spring-security-oauth2-jose

com.nimbusds

nimbus-jose-jwt

8.16

在application.yml中添加相关配置,主要是路由规则的配置、Oauth2中RSA公钥的配置及路由白名单的配置;server:

port: 9201

spring:

profiles:

active: dev

application:

name: micro-oauth2-gateway

cloud:

nacos:

discovery:

server-addr: localhost:8848

gateway:

routes: #配置路由规则

- id: oauth2-api-route

uri: lb://micro-oauth2-api

predicates:

- Path=/api/**

filters:

- StripPrefix=1

- id: oauth2-auth-route

uri: lb://micro-oauth2-auth

predicates:

- Path=/auth/**

filters:

- StripPrefix=1

discovery:

locator:

enabled: true #开启从注册中心动态创建路由的功能

lower-case-service-id: true #使用小写服务名,默认是大写

security:

oauth2:

resourceserver:

jwt:

jwk-set-uri: 'http://localhost:9401/rsa/publicKey' #配置RSA的公钥访问地址

redis:

database: 0

port: 6379

host: localhost

password:

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;

@Bean

public 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();

}

@Bean

public Converter> 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 {

@Autowired

private RedisTemplate redisTemplate;

@Override

public Mono check(Mono mono, AuthorizationContext authorizationContext) {

//从Redis中获取当前路径可访问角色列表

URI uri = authorizationContext.getExchange().getRequest().getURI();

Object obj = redisTemplate.opsForHash().get(RedisConstant.RESOURCE_ROLES_MAP, uri.getPath());

List 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);

@Override

public Mono 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);

}

@Override

public int getOrder() {

return 0;

}

}

micro-oauth2-api最后我们搭建一个API服务,它不会集成和实现任何安全相关逻辑,全靠网关来保护它。在pom.xml中添加相关依赖,就添加了一个web依赖;

org.springframework.boot

spring-boot-starter-web

在application.yml添加相关配置,很常规的配置;server:

port: 9501

spring:

profiles:

active: dev

application:

name: micro-oauth2-api

cloud:

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{

@Autowired

private 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

项目源码地址

公众号

mall项目全套学习教程连载中,关注公众号第一时间获取。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 微服务鉴权中心是指在微服务架构中,使用一个独立的鉴权中心来统一管理和验证各个微服务的访问权限。而网关是作为微服务鉴权中心的入口,负责对外提供服务,同时也是微服务的访问控制和认证的第一层防线。在网关配置中使用Spring Security OAuth2是一种常见的实现方式。 首先,需要在网关项目中引入Spring Security OAuth2的依赖。可以使用Maven或Gradle将所需的依赖加入到项目的配置文件中。 接下来,需要配置Spring Security OAuth2的相关信息,包括鉴权中心的认证服务器地址、客户端ID和密钥等。这些配置可以在网关项目的配置文件中设置。 然后,需要配置网关的请求过滤器,用于拦截并验证请求。可以实现一个自定义的过滤器,继承自Spring Security的AbstractAuthenticationProcessingFilter类,并覆写其中的相关方法。 在过滤器中,需要使用OAuth2的认证流程来验证请求的合法性。可以使用RestTemplate发送请求到鉴权中心的认证服务器,获取访问令牌和授权信息。 接着,可以通过OAuth2AuthenticationManager来管理认证信息,并创建并设置Spring Security的Authentication对象。可以使用自定义的处理器将认证信息传递给后续的微服务。 最后,需要对网关的接口进行权限的配置。可以使用Spring Security的注解来定义接口的访问权限,如@PreAuthorize和@Secured。 通过以上的配置,网关就可以实现对请求的鉴权认证了。对于未经授权的请求,网关会拒绝访问,并返回相应的错误信息。对于经过鉴权验证的请求,网关会将请求转发给相应的微服务进行处理。同时,网关还可以对返回的结果进行处理和加工,以满足特定的需求。 总之,配置Spring Security OAuth2可以有效地实现微服务鉴权中心之网关的权限管理和访问控制,提高系统的安全性和可维护性。 ### 回答2: 微服务鉴权中心作为一个独立的服务单元,负责管理和维护整个微服务体系的权限鉴权机制。网关是微服务系统的入口,负责接收和处理所有外部请求。 在配置Spring Security OAuth2时,我们可以将鉴权中心和网关进行整合,以实现统一认证和授权机制。在网关配置文件中,我们首先需要引入Spring Security OAuth2的依赖,然后定义一些必要的配置项。 首先,我们需要配置认证服务器的地址,通常是鉴权中心的地址。也就是说,所有的认证和授权请求都会转发到该地址进行处理。配置项如下: ``` security: oauth2: client: accessTokenUri: <鉴权中心地址>/oauth/token userAuthorizationUri: <鉴权中心地址>/oauth/authorize clientId: <客户端ID> clientSecret: <客户端密钥> ``` 其中,`accessTokenUri`表示获取访问令牌的地址,`userAuthorizationUri`表示用户授权的地址,`clientId`和`clientSecret`是鉴权中心针对网关颁发的客户端ID和密钥。 接下来,我们需要配置资源服务器的地址,即微服务的地址。配置项如下: ``` security: oauth2: resource: userInfoUri: <微服务地址>/user ``` 其中,`userInfoUri`表示获取用户信息的地址。 最后,我们需要配置路由规则,指定哪些请求需要进行认证和授权。配置项如下: ``` spring: cloud: gateway: routes: - id: <路由ID> uri: <目标URI> predicates: - Path=/api/** filters: - TokenRelay metadata: authorization-uri: <鉴权中心地址>/oauth/authorize ``` 其中,`id`表示路由的唯一标识,`uri`表示目标URI,`predicates`指定路由的条件,`filters`指定过滤器,`metadata`指定认证和授权的地址。 通过以上配置,我们可以实现网关和鉴权中心的整合,实现微服务系统的统一认证和授权机制。网关会将认证和授权请求转发到鉴权中心进行处理,并根据鉴权中心返回的结果进行相应的操作。 ### 回答3: 在微服务架构中,鉴权是一个非常重要的部分,它能够确保只有经过授权的用户才能访问特定的服务。而网关作为整个系统的入口,负责转发和路由请求,需要配置Spring Security和OAuth2来实现鉴权。 首先,需要在网关服务中添加Spring Security和OAuth2的相关依赖,并在配置文件中开启相关功能。接着,需要创建一个自定义的鉴权过滤器,该过滤器会在请求到达网关之后进行鉴权操作。 在过滤器中,首先需要配置一个OAuth2的资源服务。这个资源服务可以通过配置一个TokenStore来获取和保存令牌信息。然后,可以配置一个JwtAccessTokenConverter来验证和解析令牌。同时,需要配置一个ResourceServerTokenServices,该服务会验证令牌的正确性,是否过期等信息。 接下来,在过滤器中需要配置spring security的认证管理器,该管理器会根据请求头中的令牌信息来进行用户的鉴权操作。可以使用一个自定义的UserDetailsService来获取用户的权限信息,并将权限信息添加到SecurityContext中,以便后续的鉴权操作。 最后,在过滤器中,可以配置一些具体的鉴权规则,比如某些URL需要特定的角色才能访问,可以用.antMatchers().hasRole()的方式进行配置。 通过以上步骤,就可以实现网关的鉴权功能。当用户发起请求时,网关会根据请求头中的令牌信息进行鉴权,只有经过授权的用户才能访问特定的服务。这样可以确保整体系统的安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值