Spring Cloud Oauth2实现分布式权限认证(redis版)
环境:
JDK1.8,spring-boot(2.0.3.RELEASE),spring cloud(Finchley.RELEASE)
摘要说明:
微服务应用架构下,权限认证是一个不可避免的问题,此时OAuth2就是一个很好的选择;
OAuth(Open Authorization,开放授权)是为用户资源的授权定义了一个安全、开放及简单的标准,第三方无需知道用户的账号及密码,就可获取到用户的授权信息
OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0即完全废止了OAuth1;
本篇文章主要讲述用授权服务(oauth2-server)、网关服务(oauth2-gateway)、应用服务(oauth2-client)实战模拟分布式权限认证;
实际步骤如下:
1、用户通过用户名密码通过网关服务(oauth2-gateway)调用授权服务(oauth2-server)以密码模式(resource owner password credentials)进行认证授权。
2、授权服务(oauth2-server)授权成功生成token返回,并将授权信息和token保存在redis
3、后续用户进行接口调用时携带token通过网关服务(oauth2-gateway)调用应用服务(oauth2-client);应用服务会先对接口地址进行过滤拦截,通过token访问授权服务(oauth2-server)进行权限校验;
步骤:
一、整体结构及配置
代码采用聚合机构,服务主要有注册中心(eureka-server)、授权服务(oauth2-server)、网关服务(oauth2-gateway)、应用服务(oauth2-client)及公共模块(oauth2-common);
确定此次开发依靠版本:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>cn.cc.study</groupId>
<artifactId>oauth2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>
<modules>
<module>oauth2-server</module>
<module>eureka-server</module>
<module>oauth2-gateway</module>
<module>oauth2-client</module>
<module>oauth2-common</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
二、公共模块(oauth2-common)
授权服务(oauth2-server)、应用服务(oauth2-client)包含公共模块(oauth2-common);
oauth2-common抽出公共工具类用于应用服务调用;其结构如下:
依赖包如下:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.cc.study</groupId>
<artifactId>oauth2</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>oauth2-common</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.30</version>
</dependency>
</dependencies>
</project>
其中CurrentUser用于封装用户信息:
package cn.cc.study.common.dto;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.Data;
/**
*
* @模块名:oauth2-common
* @包名:cn.cc.study.common.dto
* @类名称: CurrentUser
* @类描述:【类描述】用户封装类
* @版本:1.0
* @创建人:cc
* @创建时间:2019年11月19日下午1:39:35
*/
@Data
public class CurrentUser {
private static final long serialVersionUID = 1L;
/**
* 客户端id
*/
private String clientId;
/**
* 用户id
*/
private Integer userId;
/**
* 角色列表
*/
private List < Integer > roleIds;
/**
* 用户属性
*/
private Map < String, Object > params;
private String password;
/**
* 用户名称
*/
private String username;
/**
* 用户权限
*/
private Set < Authority > authorities;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
private boolean enabled;
public CurrentUser() {
super();
}
@Data
public static class Authority {
public String authority;
Authority() {
super();
}
}
}
SecurityUtil是用于服务进行当前用户的获取:
package com.tit.cmsp.authorization.auth.client.util;
import java.util.List;
import java.util.Set;
import org.springframework.security.core.context.SecurityContextHolder;
import com.alibaba.fastjson.JSON;
import com.tit.cmsp.authorization.auth.client.api.CurrentUser;
import com.tit.cmsp.authorization.auth.client.api.CurrentUser.Authority;
/**
*
* @模块名:cmsp-authorization-common
* @包名:com.tit.cmsp.authorization.common.util
* @类名称: SecurityUtil
* @类描述:【类描述】Security工具类
* @版本:1.0
* @创建人:cc
* @创建时间:2019年9月9日下午5:26:47
*/
public class SecurityUtil {
/**
*
* @方法名:currentUser
* @方法描述【方法功能描述】获取当前用户信息
* @return
* @修改描述【修改描述】
* @版本:1.0
* @创建人:cc
* @创建时间:2019年9月9日 下午5:27:13
* @修改人:cc
* @修改时间:2019年9月9日 下午5:27:13
*/
public static CurrentUser currentUser() {
CurrentUser currentUser = JSON.parseObject(
JSON.parseObject(JSON.toJSONString(SecurityContextHolder.getContext().getAuthentication()))
.getJSONObject("userAuthentication").getJSONObject("details").getJSONObject("principal")
.toJSONString(), CurrentUser.class);
return currentUser;
}
/**
*
* @方法名:getRoleIds
* @方法描述【方法功能描述】获取当前角色id
* @return
* @修改描述【修改描述】
* @版本:1.0
* @创建人:cc
* @创建时间:2019年9月9日 下午5:28:21
* @修改人:cc
* @修改时间:2019年9月9日 下午5:28:21
*/
public static List < Integer > getRoleIds() {
return currentUser().getRoleIds();
}
/**
*
* @方法名:getRoleIds
* @方法描述【方法功能描述】获取权限集合
* @return
* @修改描述【修改描述】
* @版本:1.0
* @创建人:cc
* @创建时间:2019年9月9日 下午5:28:58
* @修改人:cc
* @修改时间:2019年9月9日 下午5:28:58
*/
public static Set < Authority > getGrantedAuthoritys() {
return currentUser().getAuthorities();
}
/**
*
* @方法名:isGrantedAuthority
* @方法描述【方法功能描述】是否含有该权限
* @param menu
* @return
* @修改描述【修改描述】
* @版本:1.0
* @创建人:cc
* @创建时间:2019年9月9日 下午5:30:49
* @修改人:cc
* @修改时间:2019年9月9日 下午5:30:49
*/
public static boolean isGrantedAuthority(String menu) {
return currentUser().getAuthorities().contains(menu);
}
/**
*
* @方法名:getParam
* @方法描述【方法功能描述】获取用户属性
* @param key
* @return
* @修改描述【修改描述】
* @版本:1.0
* @创建人:cc
* @创建时间:2019年9月9日 下午5:32:08
* @修改人:cc
* @修改时间:2019年9月9日 下午5:32:08
*/
public static Object getParam(String key) {
return currentUser().getParams().get(key);
}
}
3、授权服务(oauth2-server)
授权服务(oauth2-server)目录结构如下:
- MssWebResponseExceptionTranslator:异常翻译
- AuthorizationServerConfiguration:授权服务配置
- ResourceServerConfig:资源服务配置
- NoEncryptPasswordEncoder:自定义加密策略
- SecurityConfig:认证策略配置
- RedisTokenStore:重写RedisTokenStore
- MemberController:测试控制类
- pojo包:用于构建权限模型
- MyUserDetailService:重新UserDetailService,用于构建用户及授权信息
- DigestUtil:加密工具类
- Oauth2ServerApplication:启动类配置
下面会选择关键代码贴出,多余的见后面的源码;
服务依赖如下:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.cc.study</groupId>
<artifactId>oauth2</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>oauth2-server</artifactId>
<dependencies>
<dependency>
<groupId>cn.cc.study</groupId>
<artifactId>oauth2-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
服务配置如下:
spring:
application:
name: oauth2-server
redis:
host: 127.0.0.1
database: 0
server:
port: 9098
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://localhost:7001/eureka/
AuthorizationServerConfiguration(授权服务配置):
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import cn.cc.study.config.RedisTokenStore;
import cn.cc.study.config.error.MssWebResponseExceptionTranslator;
import cn.cc.study.service.MyUserDetailService;
import cn.cc.study.util.DigestUtil;
/**
* @模块名:oauth2-server
* @包名:cn.cc.study.config
* @类名称: AuthorizationServerConfiguration
* @类描述:【类描述】授权服务配置
* @版本:1.0
* @创建人:cc
* @创建时间:2019年11月18日上午11:16:27
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
/**
* 认证管理器
*/
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/**
* 用户认证器
*/
@Autowired
private MyUserDetailService userDetailsService;
/**
*
* @方法名:tokenStore
* @方法描述:自定义储存策略
* @return
* @修改描述:
* @版本:1.0
* @创建人:cc
* @创建时间:2019年11月19日 下午1:56:02
* @修改人:cc
* @修改时间:2019年11月19日 下午1:56:02
*/
@Bean
public TokenStore tokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
/**
* 定义令牌端点上的安全性约 束
*
* (non-Javadoc)
*
* @see org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter#configure(org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer)
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients().tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
/**
* 用于定义客户端详细信息服务的配置程序。可以初始化客户端详细信息;
*
* (non-Javadoc)
*
* @see org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter#configure(org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer)
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// clients.withClientDetails(clientDetails());
clients.inMemory().withClient("android").scopes("read").secret(DigestUtil.encrypt("android"))
.authorizedGrantTypes("password", "authorization_code", "refresh_token").and().withClient("webapp")
.scopes("read").authorizedGrantTypes("implicit").and().withClient("browser")
.authorizedGrantTypes("refresh_token", "password").scopes("read");
}
@Bean
public WebResponseExceptionTranslator webResponseExceptionTranslator() {
return new MssWebResponseExceptionTranslator();
}
/**
* 定义授权和令牌端点以及令牌服务
*
* (non-Javadoc)
*
* @see org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter#configure(org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer)
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
// 指定认证管理器
.authenticationManager(authenticationManager)
// 用户账号密码认证
.userDetailsService(userDetailsService)
// refresh_token
.reuseRefreshTokens(false)
// 指定token存储位置
.tokenStore(tokenStore()).tokenServices(defaultTokenServices());
}
/**
* <p>
* 注意,自定义TokenServices的时候,需要设置@Primary,否则报错,
* </p>
*
* @return
*/
@Primary
@Bean
public DefaultTokenServices defaultTokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
tokenServices.setSupportRefreshToken(true);
// tokenServices.setClientDetailsService(clientDetails());
// token有效期自定义设置,默认12小时
tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24 * 7);
// tokenServices.setAccessTokenValiditySeconds(60 * 60 * 12);
// refresh_token默认30天
tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24 * 7);
// tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
return tokenServices;
}
}
ResourceServerConfig(资源服务配置):主要配置antMatchers,用于进行接口url拦截设置,此处拦截/api及其子路径;若全部拦截设置成/** 或 ** 。
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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;
/**
* @模块名:oauth2-server
* @包名:cn.cc.study.config
* @类名称: ResourceServerConfig
* @类描述:【类描述】定义资源管理配置,注意antMatchers的路径
* @版本:1.0
* @创建人:cc
* @创建时间:2019年11月18日下午4:10:31
*/
@Configuration
@EnableResourceServer
@Order(3)
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().requestMatchers().antMatchers("/api/**").and().authorizeRequests().antMatchers("/api/**")
.authenticated().and().httpBasic();
}
}
SecurityConfig(认证配置):
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
import cn.cc.study.service.MyUserDetailService;
/**
* @模块名:oauth2-server
* @包名:cn.cc.study.config
* @类名称: SecurityConfig
* @类描述:【类描述】Security配置
* @版本:1.0
* @创建人:cc
* @创建时间:2019年11月18日下午4:11:12
*/
@Configuration
@EnableWebSecurity
@Order(2)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailService userDetailService;
@Bean
public PasswordEncoder passwordEncoder() {
return new NoEncryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers("/oauth/**").and().authorizeRequests().antMatchers("/oauth/**")
.authenticated().and().csrf().disable();
}
/**
* 实现认证策略
*
* (non-Javadoc)
*
* @see org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#configure(org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder)
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
}
/**
* 不定义没有password grant_type,密码模式需要AuthenticationManager支持
*
* @return
* @throws Exception
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
MyUserDetailService:自定义用户授权信息,这里面只授权hello权限用户测试:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import cn.cc.study.pojo.BaseUser;
import cn.cc.study.pojo.Menu;
import cn.cc.study.pojo.Role;
import cn.cc.study.pojo.User;
import cn.cc.study.util.DigestUtil;
/**
*
* @模块名:oauth2-server
* @包名:cn.cc.study.service
* @类名称: MyUserDetailService
* @类描述:【类描述】自定义UserDetailService
* @版本:1.0
* @创建人:cc
* @创建时间:2019年11月18日下午2:31:11
*/
@Service("userDetailService")
public class MyUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User member = new User();
member.setAccount("admin");
try {
member.setPassword(DigestUtil.encrypt("123456"));
}
catch (Exception e) {
System.out.println("加密错误");
}
if (member == null) {
throw new UsernameNotFoundException(username);
}
Set < GrantedAuthority > grantedAuthorities = new HashSet <>();
// 可用性 :true:可用 false:不可用
boolean enabled = true;
// 过期性 :true:没过期 false:过期
boolean accountNonExpired = true;
// 有效性 :true:凭证有效 false:凭证无效
boolean credentialsNonExpired = true;
// 锁定性 :true:未锁定 false:已锁定
boolean accountNonLocked = true;
List < Role > roles = new ArrayList <>();
Role role1 = new Role();
role1.setId(1);
role1.setRoleName("admin");
roles.add(role1);
List < Integer > roleIds = new ArrayList < Integer >();
for (Role role : roles) {
// 角色必须是ROLE_开头,可以在数据库中设置
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role.getRoleName());
grantedAuthorities.add(grantedAuthority);
roleIds.add(role.getId());
// 获取权限
List < Menu > menus = new ArrayList <>();
Menu menu1 = new Menu();
menu1.setId(1);
menu1.setCode("hello");
for (Menu menu : menus) {
GrantedAuthority authority = new SimpleGrantedAuthority(menu.getCode());
grantedAuthorities.add(authority);
}
}
BaseUser user = new BaseUser(member.getAccount(), member.getPassword(), enabled, accountNonExpired,
credentialsNonExpired, accountNonLocked, grantedAuthorities);
user.setClientId("test");
user.setUserId(member.getId());
user.setRoleIds(roleIds);
Map < String, Object > params = new HashMap < String, Object >();
params.put("aa", "aa");
user.setParams(params);
return user;
}
}
NumberController如下:
import java.security.Principal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.oauth2.provider.token.ConsumerTokenServices;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*
* @模块名:oauth2-server
* @包名:cn.cc.study.controller
* @类名称: MemberController
* @类描述:【类描述】权限认证控制层
* @版本:1.0
* @创建人:cc
* @创建时间:2019年11月18日下午4:34:55
*/
@RestController
@RequestMapping("/api")
public class MemberController {
@Autowired
private ConsumerTokenServices consumerTokenServices;
@GetMapping("hello")
@PreAuthorize("hasAnyAuthority('hello')")
public String hello() {
return "hello";
}
@GetMapping("query")
@PreAuthorize("hasAnyAuthority('query')")
public String query() {
return "query";
}
/**
*
* @方法名:user
* @方法描述:用于进行权限校验查询
* @param member
* @return
* @修改描述:
* @版本:1.0
* @创建人:cc
* @创建时间:2019年11月19日 下午4:07:52
* @修改人:cc
* @修改时间:2019年11月19日 下午4:07:52
*/
@GetMapping("/member")
public Principal user(Principal member) {
return member;
}
@DeleteMapping(value = "/exit")
public boolean revokeToken(String access_token) {
return consumerTokenServices.revokeToken(access_token);
}
}
4、网关服务(oauth2-gateway)
网关服务(oauth2-gateway)目录结构如下:
配置如下,主要配置转发策略及进行授权接口设置:
server:
port: 1202
spring:
application:
name: oauth2-gateway
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://localhost:7001/eureka/
zuul:
routes:
auth:
path: /auth/**
serviceId: oauth2-server
sensitiveHeaders: '*'
client:
path: /client/**
serviceId: oauth2-client
sensitiveHeaders: '*'
retryable: false
ignored-services: '*'
ribbon:
eager-load:
enabled: true
host:
connect-timeout-millis: 3000
socket-timeout-millis: 3000
add-proxy-headers: true
security:
oauth2:
client:
access-token-uri: http://localhost:${server.port}/auth/oauth/token
user-authorization-uri: http://localhost:${server.port}/auth/oauth/authorize
client-id: web
resource:
user-info-uri: http://localhost:${server.port}/auth/api/member
prefer-token-info: false
ribbon:
ReadTimeout: 3000
ConnectTimeout: 3000
MaxAutoRetries: 1
MaxAutoRetriesNextServer: 2
eureka:
enabled: true
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 3500
ZuulApplication配置如下:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableDiscoveryClient
/**
* 将OAuth2访问令牌下游转发到它所代理的服务
*/
@EnableZuulProxy
/**
* 客户端令牌中继
*/
@EnableOAuth2Sso
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
5、应用服务(oauth2-client)
结构如下:
配置如下,主要说明进行权限认证的:
spring:
application:
name: oauth2-client
server:
port: 9099
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://localhost:7001/eureka/
security:
oauth2:
resource:
id: oauth2-client
user-info-uri: http://localhost:1202/auth/api/member#验证传入的令牌
ResourceServerConfig(资源认证配置):
@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().requestMatchers().antMatchers("/api/**").and().authorizeRequests().antMatchers("/api/**")
.authenticated().and().httpBasic();
}
}
TestController(测试类):
@RestController
@RequestMapping("/api")
public class TestController {
/**
*
* @方法名:hello
* @方法描述:用于测试拥有hello的权限
* @return
* @修改描述:
* @版本:1.0
* @创建人:cc
* @创建时间:2019年11月19日 下午1:48:06
* @修改人:cc
* @修改时间:2019年11月19日 下午1:48:06
*/
@GetMapping("hello")
@PreAuthorize("hasAnyAuthority('hello')")
public String hello() {
return "hello";
}
/**
*
* @方法名:query
* @方法描述:用户测试未含有query的权限
* @return
* @修改描述:
* @版本:1.0
* @创建人:cc
* @创建时间:2019年11月19日 下午1:50:13
* @修改人:cc
* @修改时间:2019年11月19日 下午1:50:13
*/
@GetMapping("query")
@PreAuthorize("hasAnyAuthority('query')")
public String query() {
return "具有query权限";
}
/**
*
* @方法名:test
* @方法描述:未加权限则登录即可访问
* @return
* @修改描述:
* @版本:1.0
* @创建人:cc
* @创建时间:2019年11月19日 下午1:48:44
* @修改人:cc
* @修改时间:2019年11月19日 下午1:48:44
*/
@GetMapping("test")
public String test() {
return "test";
}
/**
*
* @方法名:current
* @方法描述:获取用户的方式1
* @param principal
* @return
* @修改描述:
* @版本:1.0
* @创建人:cc
* @创建时间:2019年11月19日 下午1:49:23
* @修改人:cc
* @修改时间:2019年11月19日 下午1:49:23
*/
@GetMapping("current")
public Principal current(Principal principal) {
return principal;
}
/**
*
* @方法名:current1
* @方法描述:获取用户的方式2
* @param authentication
* @return
* @修改描述:
* @版本:1.0
* @创建人:cc
* @创建时间:2019年11月19日 下午1:49:54
* @修改人:cc
* @修改时间:2019年11月19日 下午1:49:54
*/
@GetMapping("current1")
public Authentication current1(Authentication authentication) {
return authentication;
}
/**
*
* @方法名:current2
* @方法描述:获取用户的方式3
* @return
* @修改描述:
* @版本:1.0
* @创建人:cc
* @创建时间:2019年11月19日 下午1:50:02
* @修改人:cc
* @修改时间:2019年11月19日 下午1:50:02
*/
@GetMapping("current2")
public CurrentUser current2() {
return SecurityUtil.currentUser();
}
}
6、测试
1、先后启动服务:
注册中心(eureka-server),
授权服务(oauth2-server),
网关服务(oauth2-gateway),
应用服务(auth2-client)
2、进行登录,输入授权类型,用户名,密码。并设置client的名称及密码,返回token:
3、调用oauth2-server当前用户接口(http://127.0.0.1:1202/auth/api/member),这里面携带token有两种方式,一种是header里添加Authorization:token_type+token,一种添加到参数中,access_token=token:
返回:
{
"authorities": [
{
"authority": "admin"
},
{
"authority": "hello"
}
],
"details": {
"remoteAddress": "10.83.5.204",
"sessionId": null,
"tokenValue": "184113d3-f093-4e77-b927-25c74ae8bd54",
"tokenType": "Bearer",
"decodedDetails": null
},
"authenticated": true,
"userAuthentication": {
"authorities": [
{
"authority": "admin"
},
{
"authority": "hello"
}
],
"details": {
"grant_type": "password",
"username": "admin"
},
"authenticated": true,
"principal": {
"password": null,
"username": "admin",
"authorities": [
{
"authority": "admin"
},
{
"authority": "hello"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true,
"clientId": "test",
"userId": null,
"roleIds": [
1
],
"params": {
"aa": "aa"
}
},
"credentials": null,
"name": "admin"
},
"principal": {
"password": null,
"username": "admin",
"authorities": [
{
"authority": "admin"
},
{
"authority": "hello"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true,
"clientId": "test",
"userId": null,
"roleIds": [
1
],
"params": {
"aa": "aa"
}
},
"credentials": "",
"oauth2Request": {
"clientId": "android",
"scope": [
"read"
],
"requestParameters": {
"grant_type": "password",
"username": "admin"
},
"resourceIds": [],
"authorities": [],
"approved": true,
"refresh": false,
"redirectUri": null,
"responseTypes": [],
"extensions": {},
"refreshTokenRequest": null,
"grantType": "password"
},
"clientOnly": false,
"name": "admin"
}
4、测试接口权限,访问
http://127.0.0.1:1202/client/api/hello?access_token=184113d3-f093-4e77-b927-25c74ae8bd54
http://127.0.0.1:1202/client/api/query?access_token=184113d3-f093-4e77-b927-25c74ae8bd54
7、源码链接
上一篇: 7-35 有理数均值 (20分)
推荐阅读
-
通过spring-data-redis实现redis分布式缓存(增强版)
-
通过spring-data-redis实现redis分布式缓存(增强版)
-
Spring Cloud下基于OAUTH2认证授权的实现示例
-
Spring Cloud OAuth2 实现用户认证及单点登录
-
Spring Cloud实战 | 最终篇:Spring Cloud Gateway+Spring Security OAuth2集成统一认证授权平台下实现注销使JWT失效方案
-
Spring Cloud Security OAuth2 实现分布式认证授服务测试
-
Spring Cloud Oauth2实现分布式权限认证(redis版)
-
Spring Cloud Oauth2实现分布式权限认证(JWT版)
-
Spring Cloud OAuth2 实现用户认证及单点登录
-
Spring Cloud下基于OAUTH2认证授权的实现示例