Spring Cloud OAuth2(一) 搭建授权服务
概要
本文内容主要为spring cloud 授权服务的搭建,采用jwt认证。
GitHub 地址:https://github.com/fp2952/spring-cloud-base/tree/master/auth-center/auth-center-provider
添加依赖
Spring Security 及 Security 的OAuth2 扩展
<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>
启动类注解
启动类添加 @EnableAuthorizationServer 注解
@SpringCloudApplication
@EnableAuthorizationServer
@EnableFeignClients("com.peng.main.client")
public class AuthCenterProviderApplication {
public static void main(String[] args){
SpringApplication.run(AuthCenterProviderApplication.class, args);
}
}
Oauth2配置类AuthorizationServerConfigurerAdapter
AuthorizationServerConfigurerAdapter中:
- ClientDetailsServiceConfigurer:用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
- AuthorizationServerSecurityConfigurer:用来配置令牌端点(Token Endpoint)的安全约束.
- AuthorizationServerEndpointsConfigurer:用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。
主要配置如下:
配置客户端详情信息(Client Details)
ClientDetailsServiceConfigurer (AuthorizationServerConfigurer 的一个回调配置项) 能够使用内存或者JDBC来实现客户端详情服务(ClientDetailsService),Spring Security OAuth2的配置方法是编写@Configuration类继承AuthorizationServerConfigurerAdapter,然后重写void configure(ClientDetailsServiceConfigurer clients)方法,如:
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 使用JdbcClientDetailsService客户端详情服务
clients.withClientDetails(new JdbcClientDetailsService(dataSource));
}
这里使用Jdbc实现客户端详情服务,数据源dataSource不做叙述,使用框架默认的表,schema链接:
https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
配置令牌 管理 (jwtAccessTokenConverter)
JwtAccessTokenConverter是用来生成token的转换器,而token令牌默认是有签名的,且资源服务器需要验证这个签名。此处的加密及验签包括两种方式:
对称加密、非对称加密(公钥密钥)
对称加密需要授权服务器和资源服务器存储同一key值,而非对称加密可使用密钥加密,暴露公钥给资源服务器验签,本文中使用非对称加密方式,配置于AuthorizationServerConfigurerAdapter如下:
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager)
// 配置JwtAccessToken转换器
.accessTokenConverter(jwtAccessTokenConverter())
// refresh_token需要userDetailsService
.reuseRefreshTokens(false).userDetailsService(userDetailsService);
//.tokenStore(getJdbcTokenStore());
}
/**
* 使用非对称加密算法来对Token进行签名
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessToken();
// 导入证书
KeyStoreKeyFactory keyStoreKeyFactory =
new KeyStoreKeyFactory(new ClassPathResource("keystore.jks"), "mypass".toCharArray());
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
return converter;
}
通过 JDK 工具生成 JKS 证书文件,并将 keystore.jks 放入resource目录下
keytool -genkeypair -alias mytest -keyalg RSA -keypass mypass -keystore keystore.jks -storepass mypass
此处我们自定义JwtAccessToken用于添加额外用户信息
/**
* Created by fp295 on 2018/4/16.
* 自定义JwtAccessToken转换器
*/
public class JwtAccessToken extends JwtAccessTokenConverter {
/**
* 生成token
* @param accessToken
* @param authentication
* @return
*/
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
DefaultOAuth2AccessToken defaultOAuth2AccessToken = new DefaultOAuth2AccessToken(accessToken);
// 设置额外用户信息
BaseUser baseUser = ((BaseUserDetail) authentication.getPrincipal()).getBaseUser();
baseUser.setPassword(null);
// 将用户信息添加到token额外信息中
defaultOAuth2AccessToken.getAdditionalInformation().put(Constant.USER_INFO, baseUser);
return super.enhance(defaultOAuth2AccessToken, authentication);
}
/**
* 解析token
* @param value
* @param map
* @return
*/
@Override
public OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map){
OAuth2AccessToken oauth2AccessToken = super.extractAccessToken(value, map);
convertData(oauth2AccessToken, oauth2AccessToken.getAdditionalInformation());
return oauth2AccessToken;
}
private void convertData(OAuth2AccessToken accessToken, Map<String, ?> map) {
accessToken.getAdditionalInformation().put(Constant.USER_INFO,convertUserData(map.get(Constant.USER_INFO)));
}
private BaseUser convertUserData(Object map) {
String json = JsonUtils.deserializer(map);
BaseUser user = JsonUtils.serializable(json, BaseUser.class);
return user;
}
}
JwtAccessToken 类中从authentication里的getPrincipal(实际为UserDetails接口)获取用户信息,所以我们需要实现自己的UserDetails
/**
* Created by fp295 on 2018/4/29.
* 包装org.springframework.security.core.userdetails.User类
*/
public class BaseUserDetail implements UserDetails, CredentialsContainer {
private final BaseUser baseUser;
private final org.springframework.security.core.userdetails.User user;
public BaseUserDetail(BaseUser baseUser, User user) {
this.baseUser = baseUser;
this.user = user;
}
@Override
public void eraseCredentials() {
user.eraseCredentials();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getAuthorities();
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return user.isAccountNonExpired();
}
@Override
public boolean isAccountNonLocked() {
return user.isAccountNonLocked();
}
@Override
public boolean isCredentialsNonExpired() {
return user.isCredentialsNonExpired();
}
@Override
public boolean isEnabled() {
return user.isEnabled();
}
public BaseUser getBaseUser() {
return baseUser;
}
}
授权端点开放
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer
// 开启/oauth/token_key验证端口无权限访问
.tokenKeyAccess("permitAll()")
// 开启/oauth/check_token验证端口认证权限访问
.checkTokenAccess("isAuthenticated()");
}
Security 配置
需要配置 DaoAuthenticationProvider、UserDetailService 等
@Configuration
@Order(ManagementServerProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// 自动注入UserDetailsService
@Autowired
private BaseUserDetailService baseUserDetailService;
@Override
public void configure(HttpSecurity http) throws Exception {
http // 配置登陆页/login并允许访问
.formLogin().permitAll()
// 登出页
.and().logout().logoutUrl("/logout").logoutSuccessUrl("/")
// 其余所有请求全部需要鉴权认证
.and().authorizeRequests().anyRequest().authenticated()
// 由于使用的是JWT,我们这里不需要csrf
.and().csrf().disable();
}
/**
* 用户验证
* @param auth
*/
@Override
public void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(daoAuthenticationProvider());
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
// 设置userDetailsService
provider.setUserDetailsService(baseUserDetailService);
// 禁止隐藏用户未找到异常
provider.setHideUserNotFoundExceptions(false);
// 使用BCrypt进行密码的hash
provider.setPasswordEncoder(new BCryptPasswordEncoder(6));
return provider;
}
}
UserDetailsService 实现
@Service
public class BaseUserDetailService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private BaseUserService baseUserService;
@Autowired
private BaseRoleService baseRoleService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 调用FeignClient查询用户
ResponseData<BaseUser> baseUserResponseData = baseUserService.getUserByUserName(username);
if(baseUserResponseData.getData() == null || !ResponseCode.SUCCESS.getCode().equals(baseUserResponseData.getCode())){
logger.error("找不到该用户,用户名:" + username);
throw new UsernameNotFoundException("找不到该用户,用户名:" + username);
}
BaseUser baseUser = baseUserResponseData.getData();
// 调用FeignClient查询角色
ResponseData<List<BaseRole>> baseRoleListResponseData = baseRoleService.getRoleByUserId(baseUser.getId());
List<BaseRole> roles;
if(baseRoleListResponseData.getData() == null || !ResponseCode.SUCCESS.getCode().equals(baseRoleListResponseData.getCode())){
logger.error("查询角色失败!");
roles = new ArrayList<>();
}else {
roles = baseRoleListResponseData.getData();
}
// 获取用户权限列表
List<GrantedAuthority> authorities = new ArrayList();
roles.forEach(e -> {
// 存储用户、角色信息到GrantedAuthority,并放到GrantedAuthority列表
GrantedAuthority authority = new SimpleGrantedAuthority(e.getRoleCode());
authorities.add(authority);
});
// 返回带有用户权限信息的User
org.springframework.security.core.userdetails.User user = new org.springframework.security.core.userdetails.User(baseUser.getUserName(),
baseUser.getPassword(), isActive(baseUser.getActive()), true, true, true, authorities);
return new BaseUserDetail(baseUser, user);
}
private boolean isActive(int active){
return active == 1 ? true : false;
}
}
授权服务器验证
http://127.0.0.1:8080/oauth/authorize?client_id=clientId&response_type=code&redirect_uri=www.baidu.com
注意:client_id:为存储在数据库里的client_id, response_type:写死code
- 链接回车后进入spring security 的简单登陆页面,

- 输入账号密码,为实现的 UserDetailsService 要里获取的用户,点击 login,
- 进入简单授权页面,点击 Authorize,
- 重定向到 redirect_uri,并带有 code 参数:
http://www.baidu.com?code=rTKETX - post请求获取 token:

注意,此处需加 Authorization 请求头,值为 Basic xxx xxx 为 client_id:client_secret 的 base64编码。
Spring Cloud OAuth2(一) 搭建授权服务的更多相关文章
- spring cloud+dotnet core搭建微服务架构:Api授权认证(六)
前言 这篇文章拖太久了,因为最近实在太忙了,加上这篇文章也非常长,所以花了不少时间,给大家说句抱歉.好,进入正题.目前的项目基本都是前后端分离了,前端分Web,Ios,Android...,后端也基本 ...
- spring cloud+.net core搭建微服务架构:Api授权认证(六)
前言 这篇文章拖太久了,因为最近实在太忙了,加上这篇文章也非常长,所以花了不少时间,给大家说句抱歉.好,进入正题.目前的项目基本都是前后端分离了,前端分Web,Ios,Android...,后端也基本 ...
- vue+uni-app商城实战 | 第一篇:【有来小店】微信小程序快速开发接入Spring Cloud OAuth2认证中心完成授权登录
一. 前言 本篇通过实战来讲述如何使用uni-app快速进行商城微信小程序的开发以及小程序如何接入后台Spring Cloud微服务. 有来商城 youlai-mall 项目是一套全栈商城系统,技术栈 ...
- 【译文】用Spring Cloud和Docker搭建微服务平台
by Kenny Bastani Sunday, July 12, 2015 转自:http://www.kennybastani.com/2015/07/spring-cloud-docker-mi ...
- spring cloud+dotnet core搭建微服务架构:Api网关(三)
前言 国庆假期,一直没有时间更新. 根据群里面的同学的提问,强烈推荐大家先熟悉下spring cloud.文章下面有纯洁大神的spring cloud系列. 上一章最后说了,因为服务是不对外暴露的,所 ...
- spring cloud+dotnet core搭建微服务架构:配置中心续(五)
前言 上一章最后讲了,更新配置以后需要重启客户端才能生效,这在实际的场景中是不可取的.由于目前Steeltoe配置的重载只能由客户端发起,没有实现处理程序侦听服务器更改事件,所以还没办法实现彻底实现这 ...
- spring cloud+.net core搭建微服务架构:配置中心续(五)
前言 上一章最后讲了,更新配置以后需要重启客户端才能生效,这在实际的场景中是不可取的.由于目前Steeltoe配置的重载只能由客户端发起,没有实现处理程序侦听服务器更改事件,所以还没办法实现彻底实现这 ...
- spring cloud+.net core搭建微服务架构:Api网关(三)
前言 国庆假期,一直没有时间更新. 根据群里面的同学的提问,强烈推荐大家先熟悉下spring cloud.文章下面有纯洁大神的spring cloud系列. 上一章最后说了,因为服务是不对外暴露的,所 ...
- Spring Cloud和Docker搭建微服务平台
用Spring Cloud和Docker搭建微服务平台 This blog series will introduce you to some of the foundational concepts ...
- 手把手教你使用spring cloud+dotnet core搭建微服务架构:服务治理(-)
背景 公司去年开始使用dotnet core开发项目.公司的总体架构采用的是微服务,那时候由于对微服务的理解并不是太深,加上各种组件的不成熟,只是把项目的各个功能通过业务层面拆分,然后通过nginx代 ...
随机推荐
- linux下php安装
nginx中配置php: http://www.111cn.net/sys/nginx/64044.htm
- SSH配置优化和慢的解决方法
author: headsen chen date: 2018-08-18 00:28:37 ssh配置优化 vim /etc/ssh/sshd_config 1,修改root端口 2,不允许ro ...
- c# Use NAudio Library to Convert MP3 audio into WAV audio(将Mp3格式转换成Wav格式)
Have you been in need of converting mp3 audios to wav audios? If so, the skill in this article prov ...
- Unity3D笔记五 快捷键
一.近距离查看游戏对象 在Hierarchy视图中选择游戏对象,然后在Scene视图中按快捷键“F”来近距离查看该游戏对象. 二.游戏对象不在主摄像头中? Hierarchy中双击选择需要显示的游戏对 ...
- Asp.net读取和写入txt文件方法(实例)!
Asp.NET读取和写入txt文件方法(实例)! [程序第一行的引入命名空间文件 - 参考] System; using System.Collections; using System.Config ...
- 基于JDK1.8的String源码学习笔记
String,可能是学习Java一上来就学习的,经常用,但是却往往只是一知半解,甚至API有时也得现查.所以还是老规矩,倒腾源码. 一.java doc 这次首先关注String的doc,因为其实作为 ...
- 每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它
mybatis – MyBatis 3 | 入门 http://www.mybatis.org/mybatis-3/zh/getting-started.html 作用域(Scope)和生命周期 理解 ...
- nginx ---refine---按需时间/流量进行调整后台服务器---geocity,proxypass
原理相当于配置文件在启动时进行编译,proxyPass http://mydomain/path/xxxx这种方式是编译成静态的,直接替换成它解析到的ip/vip,只有重新启动时才会重新解析. 而 s ...
- 一道题浅析 i++,++i,i+1及(引用)&i的区别
我们可能很清楚i++,++i和i+1级&i的概念,但在实际运用中我们就有可能很容易搞混淆.特别是在递归中区别它们就显得尤为重要了.那首先我们先看一段利用递归逆序字符串的代码,你能回答出这段代码 ...
- CF1028E Restore Array 构造
正解:构造 解题报告: 传送门! 是的灵巧还在写构造,,,不知道484我做题太慢的缘故我感觉我做了好久的构造了然而一半的题目都没做完QAQ 要哭出来了QAQ 然后说下这题的解法,开始花了这——么的时间 ...