好好学习,天天向上
本文已收录至我的Github仓库DayDayUP:http://github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航

介绍

OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。OAuth2.0的系统大致分由客户端,认证授权服务器以及资源服务器三部分组成。客户端如果想要访问资源服务器中的资源,就必须要持有认证授权服务器颁发的Token。认证流程如下图所示:

这篇文章将通过一个具体的案例来展示如何搭建一个分布式的OAuth2.0系统。整体的结构图如下所示。有网关,认证授权服务以及资源服务三个部分组成。既然OAuth2是一个标准,如果我们想用的话,必然是用它的实现,也就是Spring-Security-OAuth2,它可以很方便地和Spring Cloud集成。OAuth2.0的更多细节会在案例中继续介绍。

那么就开始吧!

数据库

要完成这套系统,需要准备好用到的一些数据表。

  • oauth_client_details:这个数据库存放了客户端的配置信息,客户端有什么样的权限才可以访问服务器。表中的字段是固定的,下面会详细提到。
  • oauth_code:用户数据库存取授权码模式存放授权码的,表中的字段也是固定的,下面会详细说明。
  • 后面的5张表存放了用户的一些信息,如果角色、权限等信息。登录验证的时候需要。

建表的sql我放在了源码的README.md文件中,下载地址见文末。

注册中心

微服务项目得先有个注册中心吧,我们选用Eureka。先搭建一个父工程OAuth2Demo,然后在父工程中创建一个Module叫oauth2_eureka。然后添加配置文件及启动类即可。所需要的依赖我就不在这里贴了,太占篇幅了。有需要的小伙伴直接去我源码中拷就行了。

spring:application:name: eureka
server:port: 8000 #启动端口
…………@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {public static void main(String[] args) {SpringApplication.run(EurekaApplication.class,args);}
}

这样注册中心就搭建好了。

认证授权服务

服务搭建

在OAuth2Demo中创建一个Module叫oauth2_uaa作为认证服务。添加启动类和配置文件。

spring.application.name=uaa
server.port=8001
eureka.client.serviceUrl.defaultZone = http://localhost:8000/eureka/
…………
@SpringBootApplication
@EnableEurekaClient
@MapperScan("com.robod.uaa.mapper")
public class UaaApplication {public static void main(String[] args) {SpringApplication.run(UaaApplication.class, args);}
}

配置

回顾上一篇Spring Security的文章中提到的几点内容

  • 用户来源的Service实现UserDetailsService接口,实现loadUserByUsername()方法,从数据库中获取数据
  • Spring Security的配置类继承自WebSecurityConfigurerAdapter,重写里面的两个configure()方法
public interface UserService extends UserDetailsService {
}
//-----------------------------------------------------------
@Service("userService")
public class UserServiceImpl implements UserService {…………@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SysUser sysUser = userMapper.findByUsername(username);return sysUser;}
}@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {…………@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Bean@Overrideprotected AuthenticationManager authenticationManager() throws Exception {return super.authenticationManager();}//认证用户的来源@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(passwordEncoder());}//配置SpringSecurity相关信息@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/r/r1").hasAnyAuthority("p1").antMatchers("/login*").permitAll().anyRequest().authenticated().and().formLogin();}}

解释一下上面的代码,WebSecurityConfig是Spring Security的配置类,第一个configure()方法配置的是用户的来源,这里配置了自定义的实现了UserDetailsService接口的UserService,里面的loadUserByUsername()方法从数据库中查询出对应的实现了UserDetails接口的SysUser对象,里面的SysPermission封装了用户所拥有的权限。然后就交给后续的过滤器去处理了,我们就不用去管了。

然后我们就可以去进行OAuth2.0的相关配置了,方法很简单,只要在配置类上添加@EnableAuthorizationServer注解并让其继承自AuthorizationServerConfigurerAdapter。最后重写其中的三个configure()方法即可。

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {@Autowiredprivate AuthenticationManager authenticationManager;    //从WebSecurityConfig中获取的@Autowiredprivate AuthorizationCodeServices authorizationCodeServices;    //本类中的,授权码模式需要@Autowiredprivate TokenStore tokenStore;  //TokenConfig中的@Autowiredprivate PasswordEncoder passwordEncoder;//从WebSecurityConfig中获取的@Autowiredprivate ClientDetailsService clientDetailsService;   //本类中的@Autowiredprivate JwtAccessTokenConverter jwtAccessTokenConverter;    //TokenConfig中的//用来配置令牌端点的安全约束@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {security.tokenKeyAccess("permitAll")    // /oauth/token_key 提供公有密匙的端点 允许任何人访问.checkTokenAccess("permitAll")  // /oauth/check_token :用于资源服务访问的令牌解析端点 允许任何人访问.allowFormAuthenticationForClients();   //表单认证(申请令牌)}//用来配置客户端详情服务,客户端详情信息在这里进行初始化,//你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.withClientDetails(clientDetailsService);}//用来配置令牌(token)的访问端点(url)和令牌服务(token services)@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.authenticationManager(authenticationManager)  //认证管理器,密码模式需要.authorizationCodeServices(authorizationCodeServices)   //授权码服务,授权码模式需要.tokenServices(tokenService()).allowedTokenEndpointRequestMethods(HttpMethod.POST);   //允许post提交}@Beanpublic AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {//设置授权码模式的授权码存取到数据中return new JdbcAuthorizationCodeServices(dataSource);}//客户端详情服务,从数据库中获取@Beanpublic ClientDetailsService clientDetailsService(DataSource dataSource) {ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);((JdbcClientDetailsService)clientDetailsService).setPasswordEncoder(passwordEncoder);return clientDetailsService;}//令牌管理服务@Beanpublic AuthorizationServerTokenServices tokenService() {DefaultTokenServices service = new DefaultTokenServices();service.setClientDetailsService(clientDetailsService);  //客户端信息服务service.setSupportRefreshToken(true);               //支持自动刷新service.setTokenStore(tokenStore);//令牌增强TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));service.setTokenEnhancer(tokenEnhancerChain);service.setAccessTokenValiditySeconds(7200);        //令牌默认有效期2小时service.setRefreshTokenValiditySeconds(259200);     //刷新令牌默认有效期3天return service;}
}

现在来解释一下上面代码中的内容

  • ClientDetailsService
    我们配置了从数据库中获取客户端配置。但是是怎么从数据库中获取的呢,这里用到了一个JdbcClientDetailsService,点击源码里看看

可以看到,它是从 oauth_client_details 这张表里查出来的,所以我们的数据库中只要创建出这张表,表里再添加这些字段即可。

  • JdbcAuthorizationCodeServices
    原理和JdbcClientDetailsService差不多,都是创建出指定的表。
  • TokenStoreJwtAccessTokenConverter
    为了方便管理,我们使用TokenConfig这个类去配置Token相关的内容。添加了**@Bean**注解将其添加到Spring容器后就可以在其它的类中去注入使用了。
    @Configuration
    public class TokenConfig {
    private String SIGNING_KEY = "robod_hahaha"; //对称加密的密钥
    @Bean
    public TokenStore tokenStore() {//JWT令牌方案
    return new JwtTokenStore(jwtAccessTokenConverter());
    }
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    converter.setSigningKey(SIGNING_KEY); //对称秘钥,资源服务器使用该秘钥来验证
    return converter;
    }
    }
    采用了JWT令牌管理方式,然后使用了对称密钥去进行加密。还有另外几种令牌管理方式:

    • InMemoryTokenStore:在内存中存储令牌(默认)
    • JdbcTokenStore:令牌存储在数据库中
    • RedisTokenStore:令牌存储在Redis中
  • AuthorizationServerTokenServices
    这个是用来配置令牌管理服务的,我们配置了客户端详情服务,令牌增强等内容。

申请令牌的四种方式

到现在为止,我们的认证授权服务就已经配置好了,那么现在就可以去申请令牌了,申请令牌的方式一共有四种:

  • 授权码模式

第一步申请授权码
http://localhost:8001/uaa/oauth/authorize?client_id=c1&response_type=code&scope=ROLE_ADMIN&redirect_uri=http://localhost
注意,这里的client_idscoperedirect_uri都是在oauth_client_details表中设置过的,要一一对应上,否则不行,response_type授权码模式固定为code。成功访问后,在页面上输入用户名和密码,验证通过后,在浏览器的地址栏中就可以看到返回的授权码。

然后我们拿着授权码就可以向服务器去申请Token了,参数列表必须和数据库中配置的一致。

  • 简化模式
    http://localhost:8001/uaa/oauth/authorize?client_id=c1&response_type=token&scope=ROLE_ADMIN&redirect_uri=http://localhost
    在简化模式下,我们只需要去指定client_idresponse_typescoperedirect_uri即可,请求成功后,就会跳转到指定的uri界面,然后令牌就在url中。
  • 密码模式
    在密码模式下,我们需要将用户名和密码传到服务器中,验证通过后,服务器会直接将Token返回给我们
  • 客户端模式
    该模式最简单,也是最不安全的。

网关

搭建完了认证授权服务再来创建网关服务。在父工程下创建一个名为oauth2_gateway的Module。启动类没什么好说的,配置文件中有几点需要注意:

spring.application.name=gateway
server.port=8010zuul.routes.uaa.stripPrefix = false
zuul.routes.uaa.path = /uaa/**zuul.routes.order.stripPrefix = false
zuul.routes.order.path = /order/**eureka.client.serviceUrl.defaultZone = http://localhost:8000/eureka/
…………

我们配置了微服务的名称及端口,还配置了将路径为/zuul/uaa/**/zuul/order/**的请求转发给uaa和order微服务。

老样子,第一步进行一些安全配置

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/**").permitAll().and().csrf().disable();}}

我们在这里设置了可以接收任何请求,不需要任何的权限。

接下来就需要对具体的资源服务进行配置:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {public static final String RESOURCE_ID = "res1";@Autowiredprivate TokenStore tokenStore;@Overridepublic void configure(ResourceServerSecurityConfigurer resources) {resources.tokenStore(tokenStore).resourceId(RESOURCE_ID).stateless(true);}@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/uaa/**").permitAll().antMatchers("/order/**").access("#oauth2.hasScope('ROLE_API')");}}

在这里面,配置了访问认证服务不需要任何的权限。访问订单资源服务需要用户必须具有 **“ROLE_API”**的scope权限。其中注入的tokenStore和认证服务中的TokenConfig一致。

因为订单微服务还没有创建,所以我们来测试一下网关访问认证授权服务。网关的端口是8010。

来测试一下,先是通过网关获取令牌,网关微服务的端口是8010。

可以看到,申请到了令牌,说明请求成功地被转发到了认证服务。

订单资源服务

最后,我们就可以去创建资源服务了。在父工程下创建一个名为oauth2_order的Module。

第一步,先进行一些安全配置:

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/r/**").authenticated()   //所有/r/**的请求必须认证通过.anyRequest().permitAll();  //除了/r/**,其它的请求可以访问}}

这个@EnableGlobalMethodSecurity是干吗的呢?是为了开启注解权限控制的,只有开启了之后,我们才可以在需要进行权限控制的地方去添加注解实现权限控制。

接下来就是对资源服务器的配置了。在@Configuration注解的配置类上添加**@EnableResourceServer注解,然后继承自ResourceServerConfigurerAdapter类,然后重写里面的configure()**方法即可。

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {public static final String RESOURCE_ID = "res1";    //资源服务的id@Autowiredprivate TokenStore tokenStore;  //管理令牌的方式,TokenConfig中的@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {resources.resourceId(RESOURCE_ID).tokenStore(tokenStore).stateless(true);}@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/**").access("#oauth2.hasScope('ROLE_ADMIN')").and().csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);}}

接下来就是在需要进行权限控制的方法上面添加注解。

@RestController
public class OrderController {@GetMapping(value = "/r1")@PreAuthorize("hasAuthority('p1')")//拥有p1权限方可访问此urlpublic String r1() {return "访问资源成功";}}

ok!成功了。再来试一下通过网关去访问order中的资源,用一个没有权限的用户访问试试。

说明网关成功转发了我们请求,并且我们配置的权限控制也起了作用。

总结

使用OAuth2.0搭建分布式系统到这里就结束了。内容还是挺多的,希望小伙伴们能有静下心来细品。因为考虑到篇幅,很多非核心的内容我都没有贴出来,比如pom文件,配置文件的部分内容等。小伙伴们可以下载源码再配合着这篇文章看。

点击下载代码

码字不易,看完请点赞点赞点赞

要是有什么好的意见欢迎在下方留言。让我们下期再见!

DayDayUP​github.com

认证服务器的搭建_OAuth2.0分布式系统环境搭建相关推荐

  1. 搭建认证服务器 - Spring Security Oauth2.0 集成 Jwt 之 【密码认证流程】 总结

    在搭建介绍流程之前,确保您已经搭建了一个 Eureka 注册中心,因为没有注册中心的话会报错(也有可能我搭建的认证服务器是我项目的一个子模块的原因):Request execution error. ...

  2. 搭建认证服务器 - Spring Security Oauth2.0 集成 Jwt 之 【授权码认证流程】 总结

    在搭建介绍流程之前,确保您已经搭建了一个 Eureka 注册中心,因为没有注册中心的话会报错(也有可能我搭建的认证服务器是我项目的一个子模块的原因):Request execution error. ...

  3. Spark2.2.0 分布式离线搭建

    1.Spark简介 Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎.Spark是UC Berkeley AMP lab (加州大学伯克利分校的AMP实验室)所开源的类Hado ...

  4. 局域网搭建php_phpstudy8.0版本怎么搭建局域网

    先天下之忧而忧,后天下之乐而乐2020-02-06 20:51:182楼 在自己电脑设置; 你想将自己的电脑变成一个服务器吗?或许你还不知道我们自己的电脑也可摇身一变成为服务器.现在我分享一下把我们的 ...

  5. jmeter分布式环境搭建

    jmeter分布式环境搭建部署 jmeter分布式环境搭建 Jmeter运行方式:GUI和非GUI GUI.非GUI遇到的问题 如何解决? Jmeter分布式部署流程 Jmeter非GUI运行时动态参 ...

  6. 使用Owin中间件搭建OAuth2.0认证授权服务器

    前言 这里主要总结下本人最近半个月关于搭建OAuth2.0服务器工作的经验.至于为何需要OAuth2.0.为何是Owin.什么是Owin等问题,不再赘述.我假定读者是使用Asp.Net,并需要搭建OA ...

  7. Oauth2.0 认证服务器搭建

    核心 POM <dependency><groupId>org.springframework.cloud</groupId><artifactId>s ...

  8. OAuth 2.0实现分布式认证授权-jwt的认证服务器和资源服务器配置(5)

    一 jwt 1.1 jwt? JSON Web Token ( JWT )是一个开放的行业标准( RFC 7519 ),它定义了一种简介的.自包含的协议格式,用于 在通信双方传递json对象 , 传递 ...

  9. linux ftp 团队认证,linux下ftp和ftps以及ftp基于mysql虚拟用户认证服务器的搭建

    linux下ftp和ftps以及ftp基于mysql虚拟用户认证服务器的搭建 1.FTP协议:有命令和数据连接两种 命令连接,控制连接:21/tcp 数据连接: 主动模式,运行在20/tcp端口 和 ...

最新文章

  1. laravel debug,http模式,日志
  2. IntelliJ IDEA 乱码:全网最全 4 种方法完美解决 IntelliJ IDEA 控制台中文乱码问题
  3. Long Shadows Generate是一款在线使用纯CSS3实现长阴影的效果,一款强大的扁平化长投影制造器。...
  4. gradle maven_Gradle vs Maven
  5. Linux SendMail发送邮件失败诊断案例(四)
  6. CTA鼻祖带你走进量化研究的世界
  7. 求1到n的所有质数(素数)
  8. 什么叫pmt测试分析_DVT测试是什么意思?(smt术语详解)
  9. Android方向传感器
  10. PostgreSQL高效分区表实现-pg_pathman
  11. 如何快速调整Excel中图表标签位置
  12. Canvas 绘制方法过一遍
  13. assign和weak的区别
  14. [IDA Plugin] IDA插件收集
  15. 用 matplotlib 做交互式的票房分析
  16. Revit API 开发周边:对 Element 进行 Reflection
  17. 关于分布式事务、两阶段提交、一阶段提交、Best Efforts 1PC模式和事务补偿机制的研究[转]
  18. 用python制作二维码_用python做一个可视化生成二维码的工具
  19. 如何在arduino上使用315 mhz的发射模块和接收模块P1
  20. 关于最优控制的一些总结1

热门文章

  1. python 判断点在随机多边形内_初中数学必须掌握的28个考点及60个易错点,收藏起来慢慢看!...
  2. 设计模式篇——初探装饰器模式
  3. Tomcat无法访问中文路径的解决办法
  4. 【JavaNIO的深入研究4】内存映射文件I/O,大文件读写操作,Java nio之MappedByteBuffer,高效文件/内存映射...
  5. MyEclipse的安装和汉化过程
  6. Linux下防止文件误删方法
  7. mysql处理存在则更新,不存在则插入(多列唯一索引)
  8. (转) Android 数字签名
  9. CSS基础学习 19.CSS hack
  10. ABP 拦截器不工作