
在SpringCloud项目里 ,Oauth2.0密码模式 校验权限的总体流程还是 一样的。

只不过使用 密码模式获取 token,也就是说在获取 token 过程中必须带上用户的用户名和密码,获取到 的 token 是跟用户绑定的。
客户端信息和用户信息 既可以存在内存里,也可以存在 数据库里, 存在内存的方式 我们已经在 上一篇 客户端模式 演示过了,接下来 密码模式就 看下 如何在数据库 存储。
可以直接从官网上扒下来,是Oauth2.0用来权限校验 预设的表。主要用来存token,授权码和客户端信息。
CREATE TABLE `oauth_access_token` (
`token_id` varchar(256) DEFAULT NULL,
`token` blob,
`authentication_id` varchar(48) NOT NULL,
`user_name` varchar(256) DEFAULT NULL,
`client_id` varchar(256) DEFAULT NULL,
`authentication` blob,
`refresh_token` varchar(256) DEFAULT NULL,
PRIMARY KEY (`authentication_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(48) NOT NULL COMMENT '客户端ID,主要用于标识对应的应用',
`resource_ids` varchar(256) DEFAULT NULL,
`client_secret` varchar(256) DEFAULT NULL COMMENT '客户端秘钥,BCryptPasswordEncoder加密',
`scope` varchar(256) DEFAULT NULL COMMENT '对应的范围',
`authorized_grant_types` varchar(256) DEFAULT NULL COMMENT '认证模式',
`web_server_redirect_uri` varchar(256) DEFAULT NULL COMMENT '认证后重定向地址',
`authorities` varchar(256) DEFAULT NULL,
`access_token_validity` int(11) DEFAULT NULL COMMENT '令牌有效期',
`refresh_token_validity` int(11) DEFAULT NULL COMMENT '令牌刷新周期',
`additional_information` varchar(4096) DEFAULT NULL,
`autoapprove` varchar(256) DEFAULT NULL,
PRIMARY KEY (`client_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
插入 micro-order 客户端记录, 密钥为 123456的 {bcrypt}+ bcrypt加密后的 密文
INSERT INTO `oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('micro-order', 'micro-order', '{bcrypt}$2a$10$8HBBphskF43dijSHs8KQg./BWUnxeqRaFr0jbDwCcqJo0FNM6YZn2', 'all,read,writr,aa', 'client_credentials,refresh_token,password', NULL, 'oauth2', NULL, NULL, NULL, NULL);
CREATE TABLE `oauth_client_token` (
`token_id` varchar(256) DEFAULT NULL,
`token` blob,
`authentication_id` varchar(48) NOT NULL,
`user_name` varchar(256) DEFAULT NULL,
`client_id` varchar(256) DEFAULT NULL,
PRIMARY KEY (`authentication_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
CREATE TABLE `oauth_code` (
`code` varchar(256) DEFAULT NULL,
`authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
CREATE TABLE `oauth_refresh_token` (
`token_id` varchar(256) DEFAULT NULL,
`token` blob,
`authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
CREATE TABLE `oauth_approvals` (
`userId` varchar(256) DEFAULT NULL,
`clientId` varchar(256) DEFAULT NULL,
`scope` varchar(256) DEFAULT NULL,
`status` varchar(10) DEFAULT NULL,
`expiresAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`lastModifiedAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
用户信息包括角色权限 和我们的业务有关,由我们自己创建, 只需要向认证服务器框架提供 获取用户 信息 的 方法即可。
经典 用户 , 角色,用户_角色 关系表
最重要是用户名和密码
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
插入用户信息,用户名为 admin,密码为123456的 {bcrypt} + bcrypt加密后的密文
INSERT INTO `user`(`id`, `username`, `password`) VALUES (1, 'admin', '{bcrypt}$2a$10$8HBBphskF43dijSHs8KQg./BWUnxeqRaFr0jbDwCcqJo0FNM6YZn2');
CREATE TABLE `role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
插入角色 ROLE_ADMIN,注意一定要 ROLE_ 为前缀。不然会报错
INSERT INTO `role`(`id`, `name`) VALUES (1, 'ROLE_ADMIN');
CREATE TABLE `user_role` (
`user_id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
和客户端模式的依赖相同,由于需要 与数据库交互, 多引入了 数据持久化框 之类的依赖
<dependencies>
<!-- 注册到注册中心 -->
<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-web</artifactId>
</dependency>
<!-- 认证服务相关 -->
<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>org.springframework.security</groupId>
<artifactId>spring-security-data</artifactId>
</dependency>
<!--数据库操作相关, 认证服务器 需要存储token,获取用户信息,客户端信息,这里采用myql存储 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
@Configuration
// 开启认证服务器
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Autowired
private TokenStore tokenStore;
@Autowired
private UserServiceDetail userServiceDetail;
@Autowired
private ClientDetailsService clientDetailsService;
static final Logger logger = LoggerFactory.getLogger(AuthorizationServerConfiguration.class);
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
@Bean // 声明 ClientDetails实现,用数据库存储,需要配置数据源对象
public ClientDetailsService clientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// redisTokenStore
// endpoints.tokenStore(new MyRedisTokenStore(redisConnectionFactory))
// .authenticationManager(authenticationManager)
// .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
// 存数据库
endpoints.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userServiceDetail);
// 配置tokenServices参数
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
//支持refreshtoken
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
tokenServices.setAccessTokenValiditySeconds(60 * 5);
//重复使用
tokenServices.setReuseRefreshToken(false);
tokenServices.setRefreshTokenValiditySeconds(60 * 10);
endpoints.tokenServices(tokenServices);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 允许表单认证
security.allowFormAuthenticationForClients()
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
// 密码加密方式
@Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers().anyRequest()
.and()
.authorizeRequests()
// .antMatchers("/oauth/**").permitAll();
.antMatchers("/").authenticated();
/* http.csrf().disable().exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
.authorizeRequests().
antMatchers("/favicon.ico").permitAll()
.antMatchers("/oauth/**").permitAll()
.antMatchers("/login/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.and()
.httpBasic().disable();*/
http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic(); //拦截所有请求 通过httpBasic进行认证
}
}
定义了如何获取用户信息,比如用户名,密码,角色权限等,这里我们用jpa从数据库查询,也就是user,role,user_role根据用户名查询出 用户信息 交由权限框架 校验。
@Service
public class UserServiceDetail implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByUsername(username);
}
}
下游服务 调认证服务器的这个接口来验证token是否有效,token有效的话 则返回 认证后的用户信息, 让下游服务 做更细致的权限控制,比如方法级别的控制。
@Slf4j
@RestController
@RequestMapping("/security")
public class SecurityController {
@RequestMapping(value = "/check", method = RequestMethod.GET)
public Principal getUser(Principal principal) {
log.info(principal.toString());
return principal;
}
}
spring.application.name=oauth-pwd-server
#spring.cloud.controller.uri= http://localhost:9009/
server.port=9052
#eureka.client.service-url.defaultZone=http://localhost:9001/eureka/
eureka.client.serviceUrl.defaultZone=http://admin:admin@localhost:9001/eureka/
# 数据源配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://*****:3306/oauth2.0?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
logging.level.org.springframework.security=debug
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.security.basic.enabled=true
@SpringBootApplication
// 认证服务器本身也作为一种 受保护资源
@EnableResourceServer
public class OauthPwdServerApplication {
public static void main(String[] args) {
SpringApplication.run(OauthPwdServerApplication.class, args);
}
}
获取token的 接口仍然是 /oauth/token
在密码模式中 ,Authorization请求头需要填写 加密后的 客户端id和客户端密钥, 只能 用来校验 客户端 是否合法。
用postman,选择Basic Auth加密方式

这里的客户端id和 密码对应 我们一开始 保存到 oauth_client_details 表里的客户端记录
数据库里存的密文, 页面上填明文。

之后就可以看到在请求头里 多出了 客户端id和客户端密钥 生成的密钥

用户名和密码对应 user表里的 用户名和密码
数据库里存的密文, 页面上填明文。


最后调用该接口, 可以成功获取到token,证明 可以认证服务器 密码 模式 已经搭建成功。
SpringCloud项目中的需要权限校验的下游服务 就是 对应 oauth2.0的模型中, 受保护的资源服务(Resource Server),需要进行权限校验后才能 正常访问。
我们把之前的micro-order服务改成 资源服务器,让他的接口需要权限校验。
之后 调用micro-order服务 就需要传有效的token才能访问。
新增oauth2.0权限校验 依赖
<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>
/*
鉴权过滤器
* @ OAuth2AuthenticationProcessingFilter
*
* */
@EnableOAuth2Client
@EnableConfigurationProperties
@Configuration
public class OAuth2ClientConfig {
@Bean
@ConfigurationProperties(prefix = "security.oauth2.client")
public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
return new ClientCredentialsResourceDetails();
}
// @Bean
public RequestInterceptor oauth2FeignRequestInterceptor(ClientCredentialsResourceDetails clientCredentialsResourceDetails) {
return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), clientCredentialsResourceDetails);
}
@Bean
public OAuth2RestTemplate clientCredentialsRestTemplate() {
return new OAuth2RestTemplate(clientCredentialsResourceDetails());
}
}
public class RefreshTokenAuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint {
@Autowired
private ClientCredentialsResourceDetails clientCredentialsResourceDetails;
private WebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator();
@Autowired
RestTemplate restTemplate;
private static String oauth_server_url = "http://oauth-pwd-server/oauth/token";
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
try {
//解析异常,如果是401则处理
ResponseEntity<?> result = exceptionTranslator.translate(authException);
if (result.getStatusCode() == HttpStatus.UNAUTHORIZED) {
MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
formData.add("client_id", clientCredentialsResourceDetails.getClientId());
formData.add("client_secret", clientCredentialsResourceDetails.getClientSecret());
formData.add("grant_type", clientCredentialsResourceDetails.getGrantType());
formData.add("scope", String.join(",", clientCredentialsResourceDetails.getScope()));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
Map map = restTemplate.exchange(oauth_server_url, HttpMethod.POST,
new HttpEntity<MultiValueMap<String, String>>(formData, headers), Map.class).getBody();
//如果刷新异常
if (map.get("error") != null) {
// 返回指定格式的错误信息
response.setStatus(401);
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.getWriter().print("{\"code\":1,\"message\":\"" + map.get("error_description") + "\"}");
response.getWriter().flush();
//如果是网页,跳转到登陆页面
//response.sendRedirect("login");
} else {
//如果刷新成功则存储cookie并且跳转到原来需要访问的页面
for (Object key : map.keySet()) {
response.addCookie(new Cookie(key.toString(), map.get(key).toString()));
}
request.getRequestDispatcher(request.getRequestURI()).forward(request, response);
// response.sendRedirect(request.getRequestURI());
//将access_token保存
}
} else {
//如果不是401异常,则以默认的方法继续处理其他异常
super.commence(request, response, authException);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Configuration
@EnableResourceServer
//启用全局方法安全注解,就可以在方法上使用注解来对请求进行过滤
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
// @Autowired
// private TokenStore tokenStore;
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
// 配置order访问控制,必须认证后才可以访问
http.authorizeRequests()
.antMatchers("/order/**").authenticated();
}
/*
* 把token验证失败后,重新刷新token的类设置到 OAuth2AuthenticationProcessingFilter
* token验证过滤器中
* */
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
super.configure(resources);
// resources.authenticationEntryPoint(new RefreshTokenAuthenticationEntryPoint());
// resources.tokenStore(tokenStore);
}
}
新增权限校验相关配置
# 配置 认证服务器 校验token的接口地址就行
security.oauth2.resource.user-info-uri=http://127.0.0.1:9052/security/check
security.oauth2.resource.prefer-token-info=false
利用 从认证服务器获取到的token,放到Authorization请求头里, 加前缀 bearer + 空格 +token,去直接请求 micro-order 的以下接口( 从网关路由到该服务的该接口也可以, 只要网关不过滤掉 Authorization 请求信息就行)。
@RequestMapping("/order")
@RestController
@RefreshScope
public class ConfigController {
@Value("${username}")
private String username;
// 方法级别控制,必须是ROLE_ADMIN 角色才能访问
@PreAuthorize("hasRole('ROLE_ADMIN')")
@GetMapping("/getUsername")
public String getUsername(HttpServletRequest request) {
System.out.println(request.getHeader("Authorization"));
return username;
}
}

发现是可以调用成功的


至此, 资源服务器 确实 是 有权限校验的, 说明搭建成功。
加上网关zuul, 大概的校验流程就是这样的:

zuul 携带 token 请求下游系统,被下游系统 filter 拦截
下游系统过滤器根据配置中的 user-info-uri 请求到认证服务器
请求到认证服务器被 filter 拦截进行 token 校验,把 token 对应的用户、和权限从数据库
查询出来封装到 Principal .
认证服务器 token 校验通过后过滤器放行执行 security/check 接口,把 principal 对象返回
下游系统接收到 principal 对象后就知道该 token 具备的权限了,就可以进行相应用户对
应的 token 的权限执行
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende
我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel
我一直很高兴地使用DelayedJob习惯用法:foo.send_later(:bar)这会调用DelayedJob进程中对象foo的方法bar。我一直在使用DaemonSpawn在我的服务器上启动DelayedJob进程。但是...如果foo抛出异常,Hoptoad不会捕获它。这是任何这些包中的错误...还是我需要更改某些配置...或者我是否需要在DS或DJ中插入一些异常处理来调用Hoptoad通知程序?回应下面的第一条评论。classDelayedJobWorker 最佳答案 尝试monkeypatchingDelayed::W
给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最
运行bundleinstall后出现此错误:Gem::Package::FormatError:nometadatafoundin/Users/jeanosorio/.rvm/gems/ruby-1.9.3-p286/cache/libv8-3.11.8.13-x86_64-darwin-12.gemAnerroroccurredwhileinstallinglibv8(3.11.8.13),andBundlercannotcontinue.Makesurethat`geminstalllibv8-v'3.11.8.13'`succeedsbeforebundling.我试试gemin
我已经在mountainlion上成功安装了rbenv和rubybuild。运行rbenvinstall1.9.3-p392结束于:校验和不匹配:ruby-1.9.3-p392.tar.gz(文件已损坏)预期f689a7b61379f83cbbed3c7077d83859,得到1cfc2ff433dbe80f8ff1a9dba2fd5636它正在下载的文件看起来没问题,如果我使用curl手动下载文件,我会得到同样不正确的校验和。有没有人遇到过这个?他们是如何解决的? 最佳答案 tl:博士;使用浏览器从http://ftp.rub
前置步骤我们都操作完了,这篇开始介绍jenkins的集成。话不多说,看操作1、登录进入jenkins后会让你选择安装插件,选择第一个默认的就行。安装完成后设置账号密码,重新登录。2、配置JDK和Git都需要执行路径,所以需要先把执行路径找到,先进入服务器的docker容器,2.1JDK的路径root@69eef9ee86cf:/usr/bin#echo$JAVA_HOME/usr/local/openjdk-82.2Git的路径root@69eef9ee86cf:/#whichgit/usr/bin/git3、先配置JDK和Git。点击:ManageJenkins>>GlobalToolCon
大家好,我正在尝试设置一个开发环境,并且我一直在关注以下教程:Linktotutorial我做得不是很好,除了最基本的版本控制内容外,我对终端命令没有任何实际经验。我点击了第一个链接并尝试运行source~/.bash_profile我得到了错误;mkdir:/usr/local/rbenv/shims:权限被拒绝mkdir:/usr/local/rbenv/versions:权限被拒绝现在每次我加载终端时都会出现错误。bash_profile的内容;exportPATH=/usr/local/rbenv/bin:$PATHexportRBENV_ROOT=/usr/local/rbe