# security+oauth2 **Repository Path**: lide1202/security-oauth2 ## Basic Information - **Project Name**: security+oauth2 - **Description**: 介绍 spring security+oauth2四种模式 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2025-01-03 - **Last Updated**: 2025-01-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 1各个模块介绍 1 security-springmvc ,使用servlet 3.0. 基于session的认证和授权, 这个可以了解下, 如果你没用框架以前就是这么做的 2 security-spring-security , security + mvc最基础入门 3 security-springboot , security + spring boot 详解 4 distributed-security/distributed-security-uaa , 授权服务, 这里使用最简单的内存模式, 演示了oauth2的四种模式! # 2 oauth2四种模式 ## 2.1 授权码模式 ![image-20240813195709712](./assets/image-20240813195709712.png) (1)资源拥有者打开客户端,客户端要求资源拥有者给予授权,它将浏览器被重定向到授权服务器,重定向时会附加客户端的身份信息。如: /uaa/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com 参数列表如下: client_id:客户端准入标识。 response_type:授权码模式固定为code。 scope:客户端权限,是你自己配置的,代码里面配置的固定的all。 redirect_uri:跳转uri,当授权码申请成功后会跳转到此地址,并在后边带上code参数(授权码)。 第一步的redirect_uri 和第四步获取token的时候的redirect_uri 要是一样的! **(2)** 浏览器出现向授权服务器授权页面,之后将用户同意授权。\**** **(3)**授权服务器将授权码(\*******\*AuthorizationCode\*******\*)转经浏览器发送给\*******\*client(\*******\*通过\*******\*redirect_uri)\*******\*。\**** **(4)**客户端拿着授权码向授权服务器索要访问\*******\*access_token\*******\*,请求如下:\**** /uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=authorization_code&code=5PgfcD&redirect_uri=http://www.baidu.com 参数列表如下 client_id:客户端准入标识。 client_secret:客户端秘钥。 grant_type:授权类型,填写authorization_code,表示授权码模式 code:授权码,就是刚刚获取的授权码,注意:授权码只使用一次就无效了,需要重新申请。 redirect_uri:申请授权码时的跳转url,一定和申请授权码时用的redirect_uri一致。 (5)授权服务器返回令牌(access_token) 这种模式是四种模式中最安全的一种模式。一般用于client是Web服务器端应用或第三方的原生App调用资源服务的时候。因为在这种模式中access_token不会经过浏览器或移动端的App,而是直接从服务端去交换,这样就最大限度的减小了令牌泄漏的风险。 ### 测试 浏览器访问认证页面: http://localhost:53020/uaa/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com ![image-20240813195938345](./assets/image-20240813195938345.png) zhangsan 123456 或者 lisi 123456 然后输入模拟的账号和密码点登陆之后进入授权页面: ![image-20240813195952531](./assets/image-20240813195952531.png) 确认授权后,浏览器会重定向到指定路径(oauth_client_details表中的web_server_redirect_uri)并附加验证码?code=DB2mFj(每次不一样),最后使用该验证码获取token。 POST http://localhost:53020/uaa/oauth/token ![image-20240813200021788](./assets/image-20240813200021788.png) 每次请求到token后, 要是想重新获取就需要重新 获取一次授权码! ## 2.2 简化模式 ![image-20240813200052736](./assets/image-20240813200052736.png) (1)资源拥有者打开客户端,客户端要求资源拥有者给予授权,它将浏览器被重定向到授权服务器,重定向时会附加客户端的身份信息。如: /uaa/oauth/authorize?client_id=c1&response_type=token&scope=all&redirect_uri=http://www.baidu.com 参数描述同授权码模式 ,注意response_type=token,说明是简化模式。 (2)浏览器出现向授权服务器授权页面,之后将用户同意授权。 (3)授权服务器将授权码将令牌(access_token)以Hash的形式存放在重定向uri的fargment中发送给浏览器。 注:fragment 主要是用来标识 URI 所标识资源里的某个资源,在 URI 的末尾通过 (#)作为 fragment 的开头,其中 # 不属于 fragment 的值。如https://domain/index#L18这个 URI 中 L18 就是 fragment 的值。大家只需要 知道js通过响应浏览器地址栏变化的方式能获取到fragment 就行了。 一般来说,简化模式用于没有服务器端的第三方单页面应用,因为没有服务器端就无法接收授权码。 ### 测试 浏览器访问认证页面: http://localhost:53020/uaa/oauth/authorize? client_id=c1&response_type=token&scope=all&redirect_uri=http://www.baidu.com ![image-20240813200132538](./assets/image-20240813200132538.png) 然后输入模拟的账号和密码点登陆之后进入授权页面: ![image-20240813200149680](./assets/image-20240813200149680.png) 确认授权后,浏览器会重定向到指定路径(oauth_client_details表中的web_server_redirect_uri)并以Hash的形式存放在重定向uri的fargment中,如: http://aa.bb.cc/receive#access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZW5hbn... 自己测试的如下, 直接把token给你了! ![image-20240813200207064](./assets/image-20240813200207064.png) https://www.baidu.com/#access_token=91bed25e-c93f-46dc-bd83-5582041f0f4b&token_type=bearer&expires_in=6876 ## 2.3 密码模式 ![image-20240813200230455](./assets/image-20240813200230455.png) ***\*(\*******\*1\*******\*)资源拥有者将用户名、密码发送给客户端\**** ***\*(\*******\*2\*******\*)客户端拿着资源拥有者的用户名、密码向授权服务器请求令牌(\*******\*access_token\*******\*)\****,请求如下: /uaa/oauth/token? client_id=c1&client_secret=secret&grant_type=password&username=shangsan&password=123 参数列表如下: client_id:客户端准入标识。 client_secret:客户端秘钥。 grant_type:授权类型,填写password表示密码模式 username:资源拥有者用户名。 password:资源拥有者密码。 (3)授权服务器将令牌(access_token)发送给client 这种模式十分简单,但是却意味着直接将用户敏感信息泄漏给了client,因此这就说明这种模式只能用于client是我们自己开发的情况下。因此密码模式一般用于我们自己开发的,第一方原生App或第一方单页面应用。 ### 测试 POST http://localhost:53020/uaa/oauth/token 请求参数: ![image-20240813200307997](./assets/image-20240813200307997.png) ## 2.4 客户端模式 ![image-20240813200326022](./assets/image-20240813200326022.png) (1)客户端向授权服务器发送自己的身份信息,并请求令牌(access_token) (2)确认客户端身份无误后,将令牌(access_token)发送给client,请求如下: /uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=client_credentials 参数列表如下: client_id:客户端准入标识。 client_secret:客户端秘钥。 grant_type:授权类型,填写client_credentials表示客户端模式 这种模式是最方便但最不安全的模式。因此这就要求我们对client完全的信任,而client本身也是安全的。因此这种模式一般用来提供给我们完全信任的服务器端服务。比如,合作方系统对接,拉取一组用户信息。 ### 测试 POST http://localhost:53020/uaa/oauth/token ![image-20240813200402526](./assets/image-20240813200402526.png) ## 2.5 资源服务测试 ### 2.5.1 资源服务器配置 @EnableResourceServer 注解到一个 @Configuration 配置类上,并且必须使用 ResourceServerConfigurer 这个配置对象来进行配置(可以选择继承自 ResourceServerConfigurerAdapter 然后覆写其中的方法,参数就是这个 对象的实例),下面是一些可以配置的属性: ResourceServerSecurityConfigurer中主要包括: tokenServices:ResourceServerTokenServices 类的实例,用来实现令牌服务。 tokenStore:TokenStore类的实例,指定令牌如何访问,与tokenServices配置可选 resourceId:这个资源服务的ID,这个属性是可选的,但是推荐设置并在授权服务中进行验证。 其他的拓展属性例如 tokenExtractor 令牌提取器用来提取请求中的令牌。 HttpSecurity配置这个与Spring Security类似: 请求匹配器,用来设置需要进行保护的资源路径,默认的情况下是保护资源服务的全部路径。 通过http.authorizeRequests()来设置受保护资源的访问规则 其他的自定义权限保护规则通过 HttpSecurity 来进行配置。 @EnableResourceServer 注解自动增加了一个类型为 OAuth2AuthenticationProcessingFilter 的过滤器链编写ResouceServerConfig: package com.itheima.security.distributed.order.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.provider.token.TokenStore; /** \* @author Administrator \* @version 1.0 **/ @Configuration @EnableResourceServer public class ResouceServerConfig extends ResourceServerConfigurerAdapter { public static final String RESOURCE_ID = "res1"; @Autowired TokenStore tokenStore; @Override public void configure(ResourceServerSecurityConfigurer resources) { ​ resources.resourceId(RESOURCE_ID)//资源 id ​ .tokenStore(tokenStore) // .tokenServices(tokenService())//验证令牌的服务 ​ .stateless(true); } @Override public void configure(HttpSecurity http) throws Exception { ​ http ​ .authorizeRequests() ​ .antMatchers("/**").access("#oauth2.hasScope('ROLE_ADMIN')") ​ .and().csrf().disable() ​ .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } //资源服务令牌解析服务 /* @Bean public ResourceServerTokenServices tokenService() { ​ //使用远程服务请求授权服务器校验token,必须指定校验token 的url、client_id,client_secret ​ RemoteTokenServices service=new RemoteTokenServices(); ​ service.setCheckTokenEndpointUrl("http://localhost:53020/uaa/oauth/check_token"); ​ service.setClientId("c1"); ​ service.setClientSecret("secret"); ​ return service; }*/ } ### 2.5.2 验证token ResourceServerTokenServices 是组成授权服务的另一半,如果你的授权服务和资源服务在同一个应用程序上的话,你可以使用 DefaultTokenServices ,这样的话,你就不用考虑关于实现所有必要的接口的一致性问题。如果你的资源服务器是分离开的,那么你就必须要确保能够有匹配授权服务提供的 ResourceServerTokenServices,它知道如何对令牌进行解码。 令牌解析方法: 使用 DefaultTokenServices 在资源服务器本地配置令牌存储、解码、解析方式 使用RemoteTokenServices 资源服务器通过 HTTP 请求来解码令牌,每次都请求授权服务器端点 /oauth/check_token 使用授权服务的 /oauth/check_token 端点你需要在授权服务将这个端点暴露出去,以便资源服务可以进行访问,这在咱们授权服务配置中已经提到了,下面是一个例子,在这个例子中,我们在授权服务中配置了 /oauth/check_token 和 /oauth/token_key 这两个端点: @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security .tokenKeyAccess("permitAll()")// /oauth/token_key 安全配置 .checkTokenAccess("permitAll()") // /oauth/check_token 安全配置 } 在资源 服务配置RemoteTokenServices ,在ResouceServerConfig中配置: //资源服务令牌解析服务 @Bean public ResourceServerTokenServices tokenService() { //使用远程服务请求授权服务器校验token,必须指定校验token 的url、client_id,client_secret RemoteTokenServices service=new RemoteTokenServices(); service.setCheckTokenEndpointUrl("http://localhost:53020/uaa/oauth/check_token"); service.setClientId("c1"); service.setClientSecret("secret"); return service; } @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.resourceId(RESOURCE_ID) .tokenServices(tokenService()) .stateless(true); } ![image-20240813204034228](./assets/image-20240813204034228.png) ### 2.5.3 编写资源 在controller包下编写OrderController,此controller表示订单资源的访问类: @GetMapping(value = "/r1") @PreAuthorize("hasAuthority('p1')")//拥有p1权限方可访问此url public String r1(){ ​ //获取用户身份信息 ​ Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); ​ UserDTO userDTO = null; ​ if (principal instanceof UserDTO){ ​ userDTO = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); ​ } else if (principal instanceof String) { ​ userDTO = JSONObject.parseObject(principal.toString(), UserDTO.class); ​ } else { ​ throw new RuntimeException("获取当前用户信息错误"); ​ } ​ return userDTO.getFullname()+"访问资源1"; } 然后测试下 ![image-20240813204107427](./assets/image-20240813204107427.png) 因为当前用户有 p1 和 p3 权限, 所以可以访问上面的 @PreAuthorize("hasAuthority('p1')")//拥有p1权限方可访问此url