理论
OAuth
是一个关于授权(authorization
)的开放网络标准,用来授权第三方应用获取用户数据,是目前最流行的授权机制,它当前的版本是2.0。
应用场景
假如你正在“网站A”上冲浪,看到一篇帖子表示非常喜欢,当你情不自禁的想要点赞时,它会提示你进行登录操作。
打开登录页面你会发现,除了最简单的账户密码登录外,还为我们提供了微博、微信、QQ等快捷登录方式。假设选择了快捷登录,它会提示我们扫码或者输入账号密码进行登录。
登录成功之后便会将QQ/微信的昵称和头像等信息回填到“网站A”中,此时你就可以进行点赞操作了。
名词定义
在详细讲解oauth2
之前,我们先来了解一下它里边用到的名词定义吧:
- Client:客户端,它本身不会存储用户快捷登录的账号和密码,只是通过资源拥有者的授权去请求资源服务器的资源,即例子中的网站A;
- Resource Owner:资源拥有者,通常是用户,即例子中拥有QQ/微信账号的用户;
- Authorization Server:认证服务器,可以提供身份认证和用户授权的服务器,即给客户端颁发
token
和校验token
; - Resource Server:资源服务器,存储用户资源的服务器,即例子中的QQ/微信存储的用户信息;
认证流程
如图是oauth2
官网的认证流程图,我们来分析一下:
- A客户端向资源拥有者发送授权申请;
- B资源拥有者同意客户端的授权,返回授权码;
- C客户端使用授权码向认证服务器申请令牌
token
; - D认证服务器对客户端进行身份校验,认证通过后发放令牌;
- E客户端拿着认证服务器颁发的令牌去资源服务器请求资源;
- F资源服务器校验令牌的有效性,返回给客户端资源信息;
为了大家更好的理解,阿Q特地画了一张图:
到这儿,相信大家对理论知识已经掌握的差不多了,接下来我们就进入实战训练吧。
实战
在正式开始搭建项目之前我们先来做一些准备工作:要想使用oauth2
的服务,我们得先创建几张表。
数据库
oauth2
相关的建表语句可以参考官方初始化sql,也可以查看阿Q项目中的init.sql文件,回复“oauth2”获取源码。
至于表结构,大家可以先大体了解下,其中字段的含义,在init.sql文件中阿Q已经做了说明。
- oauth_client_details:存储客户端的配置信息,操作该表的类主要是
JdbcClientDetailsService.java
; - oauth_access_token:存储生成的令牌信息,操作该表的类主要是
JdbcTokenStore.java
; - oauth_client_token:在客户端系统中存储从服务端获取的令牌数据,操作该表的类主要是
JdbcClientDetailsService.java
; - oauth_code:存储授权码信息与认证信息,即只有
grant_type
为authorization_code
时,该表才会有数据,操作该表的类主要是JdbcAuthorizationCodeServices.java
; - oauth_approvals:存储用户的授权信息;
- oauth_refresh_token:存储刷新令牌的
refresh_token
,如果客户端的grant_type
不支持refresh_token
,那么不会用到这张表,操作该表的类主要是JdbcTokenStore
;
在oauth_client_details
表中添加一条数据
1 client_id:cheetah_one //客户端名称,必须唯一 2 resource_ids:product_api //客户端所能访问的资源id集合,多个资源时用逗号(,)分隔 3 client_secret:$2a$10$h/TmLPvXozJJHXDyJEN22ensJgaciomfpOc9js9OonwWIdAnRQeoi //客户端的访问密码 4 scope:read,write //客户端申请的权限范围,可选值包括read,write,trust。若有多个权限范围用逗号(,)分隔 5 authorized_grant_types:client_credentials,implicit,authorization_code,refresh_token,password //指定客户端支持的grant_type,可选值包括authorization_code,password,refresh_token,implicit,client_credentials, 若支持多个grant_type用逗号(,)分隔 6 web_server_redirect_uri:http://www.baidu.com //客户端的重定向URI,可为空, 当grant_type为authorization_code或implicit时, 在Oauth的流程中会使用并检查与注册时填写的redirect_uri是否一致 7 access_token_validity:43200 //设定客户端的access_token的有效时间值(单位:秒),可选, 若不设定值则使用默认的有效时间值(60 * 60 * 12, 12小时) 8 autoapprove:false //设置用户是否自动Approval操作, 默认值为 'false', 可选值包括 'true','false', 'read','write'
数据库中对密码进行了加密处理,大家可以在此路径下自行生成
用户角色相关的表也在init.sql文件中,表结构非常简单,大家自行查阅。我的初始化数据为
依赖引入
1 <dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-web</artifactId> 4 </dependency> 5 <dependency> 6 <groupId>org.springframework.cloud</groupId> 7 <artifactId>spring-cloud-starter-security</artifactId> 8 </dependency> 9 <dependency> 10 <groupId>org.springframework.cloud</groupId> 11 <artifactId>spring-cloud-starter-oauth2</artifactId> 12 </dependency> 13 <dependency> 14 <groupId>org.springframework.security</groupId> 15 <artifactId>spring-security-jwt</artifactId> 16 </dependency>
至于其它依赖,大家可以根据需要自行引入,不再赘述,回复“oauth2”获取源码。
资源服务
配置文件对服务端口、应用名称、数据库、mybatis
和日志进行了配置。
写了一个简单的控制层代码,用来模拟资源访问
1 @RestController 2 @RequestMapping("/product") 3 public class ProductController { 4 5 @GetMapping("/findAll") 6 public String findAll(){ 7 return "产品列表查询成功"; 8 } 9 }
接着创建配置类继承ResourceServerConfigurerAdapter
并增加@EnableResourceServer
注解开启资源服务,重写两个configure
方法
1 /** 2 * 指定token的持久化策略 3 * InMemoryTokenStore 表示将token存储在内存中 4 * RedisTokenStore 表示将token存储在redis中 5 * JdbcTokenStore 表示将token存储在数据库中 6 * @return 7 */ 8 @Bean 9 public TokenStore jdbcTokenStore(){ 10 return new JdbcTokenStore(dataSource); 11 } 12 13 /** 14 * 指定当前资源的id和token的存储策略 15 * @param resources 16 * @throws Exception 17 */ 18 @Override 19 public void configure(ResourceServerSecurityConfigurer resources) throws Exception { 20 //此处的id可以写在配置文件中,这里我们先写死 21 resources.resourceId("product_api").tokenStore(jdbcTokenStore()); 22 } 23 24 25 /** 26 * 设置请求权限和header处理 27 * @param http 28 * @throws Exception 29 */ 30 @Override 31 public void configure(HttpSecurity http) throws Exception { 32 //固定写法 33 http.authorizeRequests() 34 //指定不同请求方式访问资源所需的权限,一般查询是read,其余都是write 35 .antMatchers(HttpMethod.GET,"/**").access("#oauth2.hasScope('read')") 36 .antMatchers(HttpMethod.POST,"/**").access("#oauth2.hasScope('write')") 37 .antMatchers(HttpMethod.PATCH,"/**").access("#oauth2.hasScope('write')") 38 .antMatchers(HttpMethod.PUT,"/**").access("#oauth2.hasScope('write')") 39 .antMatchers(HttpMethod.DELETE,"/**").access("#oauth2.hasScope('write')") 40 .and() 41 .headers().addHeaderWriter((request,response) -> { 42 //域名不同或者子域名不一样并且是ajax请求就会出现跨域问题 43 //允许跨域 44 response.addHeader("Access-Control-Allow-Origin","*"); 45 //跨域中会出现预检请求,如果不能通过,则真正请求也不会发出 46 //如果是跨域的预检请求,则原封不动向下传递请求头信息,否则预检请求会丢失请求头信息(主要是token信息) 47 if(request.getMethod().equals("OPTIONS")){ 48 response.setHeader("Access-Control-Allow-Methods",request.getHeader("Access-Control-Allow-Methods")); 49 response.setHeader("Access-Control-Allow-Headers",request.getHeader("Access-Control-Allow-Headers")); 50 } 51 }); 52 }
当然我们也可以配置忽略校验的url
,在上边的public void configure(HttpSecurity http) throws Exception
中进行配置
1 ExpressionUrlAuthorizationConfigurer<HttpSecurity> 2 .ExpressionInterceptUrlRegistry config = http.requestMatchers().anyRequest() 3 .and() 4 .authorizeRequests(); 5 properties.getUrls().forEach(e -> { 6 config.antMatchers(e).permitAll(); 7 });
因为我们是需要进行校验的,所以我把对应的代码给注释掉了,大家可以回复“oauth2”下载源码自行查看。
然后将实现了UserDetails
的SysUser
和实现了GrantedAuthority
的SysRole
放到项目中,当请求发过来时,oauth2
会帮我们自行校验。
认证服务
配置文件对服务端口、应用名称、数据库、mybatis
和日志进行了配置。
Security配置
还是和之前Security+JWT组合拳的配置大同小异,不了解的可以先看下该文。
①将继承了UserDetailsService
的ISysUserService
的实现类SysUserServiceImpl
重写loadUserByUsername
方法
1 @Override 2 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 3 return this.baseMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username)); 4 }
②继承WebSecurityConfigurerAdapter
类,增加@EnableWebSecurity
注解并重写方法
1 /** 2 * 指定认证对象的来源和加密方式 3 * @param auth 4 * @throws Exception 5 */ 6 @Override 7 public void configure(AuthenticationManagerBuilder auth) throws Exception { 8 auth.userDetailsService(userService).passwordEncoder(passwordEncoder()); 9 } 10 11 /** 12 * 安全拦截机制(最重要) 13 * @param httpSecurity 14 * @throws Exception 15 */ 16 @Override 17 public void configure(HttpSecurity httpSecurity) throws Exception { 18 httpSecurity 19 //CSRF禁用,因为不使用session 20 .csrf().disable() 21 .authorizeRequests() 22 //登录接口和静态资源不需要认证 23 .antMatchers("/login*","/css/*").permitAll() 24 //除上面的所有请求全部需要认证通过才能访问 25 .anyRequest().authenticated() 26 //返回HttpSecurity以进行进一步的自定义,证明是一次新的配置的开始 27 .and() 28 .formLogin() 29 //如果未指定此页面,则会跳转到默认页面 30 // .loginPage("/login.html") 31 .loginProcessingUrl("/login") 32 .permitAll() 33 //认证失败处理类 34 .failureHandler(customAuthenticationFailureHandler); 35 } 36 37 /** 38 * AuthenticationManager 对象在OAuth2.0认证服务中要使用,提前放入IOC容器中 39 * @return 40 * @throws Exception 41 */ 42 @Override 43 @Bean 44 public AuthenticationManager authenticationManagerBean() throws Exception { 45 return super.authenticationManagerBean(); 46 }
AuthorizationServer配置
①继承AuthorizationServerConfigurerAdapter
类,增加@EnableAuthorizationServer
注解开启认证服务
②依赖注入,注入7个实例Bean
对象
1 /** 2 * 数据库连接池对象 3 */ 4 private final DataSource dataSource; 5 6 /** 7 * 认证业务对象 8 */ 9 private final ISysUserService userService; 10 11 /** 12 * 授权码模式专用对象 13 */ 14 private final AuthenticationManager authenticationManager; 15 16 /** 17 * 客户端信息来源 18 * @return 19 */ 20 @Bean 21 public JdbcClientDetailsService jdbcClientDetailsService(){ 22 return new JdbcClientDetailsService(dataSource); 23 } 24 25 /** 26 * token保存策略 27 * @return 28 */ 29 @Bean 30 public TokenStore tokenStore(){ 31 return new JdbcTokenStore(dataSource); 32 } 33 34 /** 35 * 授权信息保存策略 36 * @return 37 */ 38 @Bean 39 public ApprovalStore approvalStore(){ 40 return new JdbcApprovalStore(dataSource); 41 } 42 43 /** 44 * 授权码模式数据来源 45 * @return 46 */ 47 @Bean 48 public AuthorizationCodeServices authorizationCodeServices(){ 49 return new JdbcAuthorizationCodeServices(dataSource); 50 }
③重写方法进行配置
1 /** 2 * 用来配置客户端详情服务(ClientDetailsService) 3 * 客户端详情信息在这里进行初始化 4 * 指定客户端信息的数据库来源 5 * @param clients 6 * @throws Exception 7 */ 8 @Override 9 public void configure(ClientDetailsServiceConfigurer clients) throws Exception { 10 clients.withClientDetails(jdbcClientDetailsService()); 11 } 12 13 /** 14 * 检测 token 的策略 15 * @param security 16 * @throws Exception 17 */ 18 @Override 19 public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { 20 security 21 //允许客户端以form表单的方式将token传达给我们 22 .allowFormAuthenticationForClients() 23 //检验token必须需要认证 24 .checkTokenAccess("isAuthenticated()"); 25 } 26 27 28 /** 29 * OAuth2.0的主配置信息 30 * @param endpoints 31 * @throws Exception 32 */ 33 @Override 34 public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { 35 endpoints 36 //刷新token时会验证当前用户是否已经通过认证 37 .userDetailsService(userService) 38 .approvalStore(approvalStore()) 39 .authenticationManager(authenticationManager) 40 .authorizationCodeServices(authorizationCodeServices()) 41 .tokenStore(tokenStore()); 42 }
标签:Exception,OAuth2,return,throws,token,详解,public,客户端 From: https://www.cnblogs.com/midiyu/p/16924013.html