Spring Cloud下基于OAUTH2认证授权的实现

转自:https://blog.csdn.net/qq_34490951/article/details/79930270

在Spring Cloud需要使用OAUTH2来实现多个微服务的统一认证授权,通过向OAUTH服务发送某个类型的grant type进行集中认证和授权,从而获得access_token,而这个token是受其他微服务信任的,我们在后续的访问可以通过access_token来进行,从而实现了微服务的统一认证授权。
整体架构


验证流程


 

一般的使用流程:1、用户通过登陆请求致auth-server来通过用户信息获取Token,在之后的请求中使用Token,2、在请求致其他需要权限微服务时,服务会根据Token来获取用户信息【包含权限】来鉴权校验,3、需要权限微服务需要制定安全规则,标明每个方法所需权限,同时指明获取用户信息位置【auth-server】。

auth-server:OAUTH2认证授权中心
order-service:普通微服务,用来验证认证和授权
api-gateway:边界网关(所有微服务都在它之后)
OAUTH2中的角色:

Resource Server:被授权访问的资源
Authotization Server:OAUTH2认证授权中心
Resource Owner: 用户
Client:使用API的客户端(如Android 、IOS、web app)
Grant Type:

Authorization Code:用在服务端应用之间
Implicit:用在移动app或者web app(这些app是在用户的设备上的,如在手机上调起微信来进行认证授权)
Resource Owner Password Credentials(password):应用直接都是受信任的(都是由一家公司开发的,本例子使用)
Client Credentials:用在应用API访问。
1.基础环境
使用MongoDB作为账户存储,Redis作为Token存储。

项目依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
<version>1.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<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.springframework.security</groupId>
<artifactId>spring-security-data</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
 


 
2.auth-server
2.1 OAuth2服务配置

Redis用来存储token,服务重启后,无需重新获取token.

 

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
//认证管理器
@Autowired
AuthenticationManager authenticationManager;
//存储链接
@Autowired
RedisConnectionFactory redisConnectionFactory;
//用户信息相关的实现
@Autowired
private UserDetailsService userDetailsService;
//token存放位置
@Bean
public RedisTokenStore tokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
//配置认证管理器以及用户信息业务实现
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)//若无,refresh_token会有UserDetailsService is required错误
.tokenStore(tokenStore());
}
//配置认证规则,那些需要认证那些不需要
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
//配置客户端
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("android")//客户端账户
.scopes("xx")//作用域
.secret("android")//客户端密码
.authorizedGrantTypes("password", "authorization_code", "refresh_token")//授权方式
.and()//不同的客户端链接
.withClient("webapp")
.scopes("xx")
.authorizedGrantTypes("implicit");
}
 


 
2.2 Resource服务配置

auth-server提供user信息,所以auth-server也是一个Resource Server

package com.yangcuncloud.consumer.annotation;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import javax.servlet.http.HttpServletResponse;
/**
* Resource Server
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig
extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
提供用户信息以供鉴权

package com.yangcuncloud.consumer.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.SessionAttributes;
import java.security.Principal;
/**
* 通过token获取用户信息
*/
@RestController
public class UserController {
@GetMapping("/user")
public Principal user(Principal user){
return user;
}
}
 

2.3 安全配置

package com.yangcuncloud.consumer.annotation;
import com.yangcuncloud.consumer.security.DomainUserDetailsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension;
/**
* 安全配置相关
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
/**
*
*注意: @EnableGlobalMethodSecurity 可以配置多个参数:
*prePostEnabled :决定Spring Security的前注解是否可用 [@PreAuthorize,@PostAuthorize,..] 此处表明可用
*secureEnabled : 决定是否Spring Security的保障注解 [@Secured] 是否可用
*jsr250Enabled :决定 JSR-250 annotations 注解[@RolesAllowed..] 是否可用.
*/
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//用户信息业务类
@Bean
public UserDetailsService userDetailsService() {
return new DomainUserDetailsService();
}
//密码加密器
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//验证用户信息与密码
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder());
}
@Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
//不定义没有password grant_type
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
 


 
2.4 权限设计

采用用户(SysUser)<->角色(SysRole)<->权限(SysAuthotity)设置,彼此之间的关系是多对多。通过DomainUserDetailsService 加载用户和权限。

2.5 配置


 
server:
port: 8058
spring:
application:
name: consumer
cloud:
config:
label: master
profile: dev
discovery:
enabled: true
service-id: config
bus:
enabled: true
redis:
host: 172.16.20.19
Port: 6379
management:
security:
enabled: false
eureka:
client:
serviceUrl:
defaultZone: http://172.16.20.19:8051/eureka/
logging.level.org.springframework.security: DEBUG
logging.leve.org.springframework: DEBUG
security:
oauth2:
resource:
filter-order: 3
 

3.Resource
3.1 Resource服务配置,服务有需要受保护接口时,需要添加以下配置

package com.yangcuncloud.consumer.annotation;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import javax.servlet.http.HttpServletResponse;
/**
* Resource Server
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig
extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
 

3.2 用户信息配置
微服务使用auth-server进行认证授权,在它的配置文件指定用户信息在auth-server的地址即可:

security:
oauth2:
resource:
id: 服务id
user-info-uri: http://验证服务ip:port/用户信息接口
prefer-token-info: false
 

security: oauth2: resource: id: order-service user-info-uri: http://auth-serverHOST:port/用户信息接口 prefer-token-info: false
3.3 权限测试控制器

在需要鉴权的接口上添加注解

@PreAuthorize("hasAuthority('query-demo')")
此处的‘query-demo’就是对应的权限名

@PreAuthorize("hasAuthority('query-demo')")
@RequestMapping(value = "/userList")//此注解标明调用该接口的TOken用户需要拥有query-demo的权限
public ResponseEntity<JSONObject> userList(@RequestParam(value = "page") int page,
@RequestParam(value = "pageSzie") int pageSzie,
@RequestParam(value = "param", required = false) String param,
@RequestParam(value = "order", required = false) String sort) {

具备authority的query-demo权限的才能访问

演示
 客户端调用

使用Postman向http://localhost:8080/consumer/oauth/token发送请求获得access_token

注意此出需要添加authorization header,为我们之前的客户端账户和客户端密码(都为android)

 

之后我们使用获取的Token来验证鉴权是否有效,先使用无Token和错误Token请求

 

可以看到都是不可访问的情况,然后我们来尝试使用正确Token调用

访问成功!

然后我们修改该接口的权限,并重启服务,可以看到重启后原Token依然可用,因为我们将其保存至Redis中了。此时我们看一下该接口是否依然可用。【权限错误的情况】

再次请求

显示不允许访问。

6 注销oauth2
6.1 增加自定义注销Endpoint

所谓注销只需将access_token和refresh_token失效即可,我们模仿org.springframework.security.oauth2.provider.endpoint.TokenEndpoint写一个使access_token和refresh_token失效的Endpoint:

@FrameworkEndpoint
public class RevokeTokenEndpoint {
@Autowired
@Qualifier("consumerTokenServices")
ConsumerTokenServices consumerTokenServices;
@RequestMapping(method = RequestMethod.DELETE, value = "/oauth/token")
@ResponseBody
public String revokeToken(String access_token) {
if (consumerTokenServices.revokeToken(access_token)){
return "注销成功";
}else{
return "注销失败";
}
}
}
 


 
6.2 注销请求方式

在注销之后使用原token再次访问就会错误invalid_tokenInvalid access token: 需要重新获取token。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值