# security-auth **Repository Path**: sonarone/security-auth ## Basic Information - **Project Name**: security-auth - **Description**: springboot 2.3.8.RELEASE spring-cloud Hoxton.SR9 整合springsecurity, 实现认证服务器和资源服务器分离,测试基于授权码模式与密码模式,测试ok - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2023-12-30 - **Last Updated**: 2024-01-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # security-auth 本地文件目录:D:\VRV\security-demo\security-auth github仓库:https://gitee.com/wh1107066/security-auth ## 一、项目声明 ### 1.组件版本 springboot、springcloud、springsecurity | 组件 | 版本 | | -------------- | ------------------------------------------------------------ | | springboot | 2.3.8.RELEASE | | springcloud | Hoxton.SR9 | | springsecurity | 2.3.8.RELEASE | ### 2.新建数据库 导入数据库 ![image-20210612120354449](security测试.assets/image-20210612120354449.png) - 导入初始化建表和建库语句 init.sql - 导入json的测试数据,测试用例:安装谷歌浏览器的插件进行测试,选择所有然后进行导入 ![image-20210612115844470](security测试.assets/image-20210612115844470.png) > (因为本项目采用redis的形式存储token相关) > >   建表sql语句的地址为 [oauth2基础表链接](https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql) 这儿记得不同的数据库中的特殊字段需要修改一下 ### 3.导入工程 github仓库:https://gitee.com/wh1107066/security-auth ## 二、Oauth2说明 ### 1. 添加依赖 授权服务是基于Spring Security的,因此需要在项目中引入两个依赖: ```shell org.springframework.cloud spring-cloud-starter-security org.springframework.cloud spring-cloud-starter-oauth2 ``` 前者为 Security,后者为Security的OAuth2扩展。 ### 2. 开发说明 访问后端api需要携带上token,支持如下两种形式 1. 放header方式 ``` Authorization:Bearer xxx # 携带的Authorization Bearer xxx , # 携带的Authorization Bearer xxx , xxx指的是请求返回的access_token # 先走UserDetailsService.java 接口的实现类,然后在进入目标请求Controller的方法体。 ``` 2. 携带access_token参数方式, ``` http://localhost:9060/d00002?access_token=XXX ``` 如果不使用security.oauth2.resource的配置去处理,则需要使用代码的方式进行token的验证。 ```java # 针对资源的直接使用http的处理请求方式, 重新userDetailsService接口,进行资源的权限认证。 # mss-oauth 是注册到nacos中的服务名称,需要获取用户Authentication的信息 # 可以自定义oauth2的资源服务器进行验证处理, 这块如果不放开,就需要自己完善UserDetailsService接口,未验证 ???? security: oauth2: resource: # user-info-uri: http://mss-oauth/user #要获取请求令牌的身份验证,在mss-oauth服务器中会调用/user请求,可以打断点,每次都会重复请求。 user-info-uri: http://mss-oauth:9030/user #可以使用nginx负载,也可以直接使用上面的,还可以直接使用某一个服务的接口调用, 使用127.0.0.1:9030无效,必须使用服务名称才可以,否则会域名无法解析的错误。 prefer-token-info: false loadBalanced: true ``` 携带access_token进行接口的访问 在访问接口的过程中,后端代码可以使用 @PreAuthorize("hasAuthority('query-demo')") 进行的权限判断及接口绑定,这个是针对**写死**的情况下,**动态不能够调节权限的更改**,可以采用mss-gateway的方式进行com.microservice.skeleton.gateway.service.PermissionService的接口去实现,权限的部分继承 extends ResourceServerConfigurerAdapter , 然后public void configure(HttpSecurity http) 重写该方法。 ```java registry.anyRequest().access("@permissionService.hasPermission(request,authentication)"); ``` 资源服务器重写:(进行url的鉴权) ```java @Override public void configure(HttpSecurity http) throws Exception { // 包括token的生成与刷新操作 http.authorizeRequests().antMatchers("/v2/api-docs","/oauth/token","/mss-oauth/oauth/token").permitAll(); ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry = http.authorizeRequests(); for (String au : AUTH_WHITELIST) { http.authorizeRequests().antMatchers(au).permitAll(); } http.authorizeRequests().anyRequest().authenticated(); registry.anyRequest().access("@rbacService.hasPermission(request,authentication)"); } ``` > 可以查看开源项目: ruoyi, > > ~~~shell > public interface RbacService { > boolean hasPermission(HttpServletRequest request, Authentication authentication); > } > ~~~ > > ~~~shell > @Component("rbacService") > public class RbacServiceImpl implements RbacService { > > private AntPathMatcher antPathMatcher = new AntPathMatcher(); > > @Override > public boolean hasPermission(HttpServletRequest request, Authentication authentication) { > Object principal = authentication.getPrincipal(); > > boolean hasPermission = false; > > if (principal instanceof Admin) { > //如果用户名是admin,就永远返回true > if (StringUtils.equals(((Admin) principal).getUsername(), "admin")) { > hasPermission = true; > } else { > // 读取用户所拥有权限的所有URL > Set urls = ((Admin) principal).getUrls(); > for (String url : urls) { > if (antPathMatcher.match(url, request.getRequestURI())) { > hasPermission = true; > break; > } > } > } > } > > return hasPermission; > } > > } > ~~~ ### 3.认证服务器 数据库建好后,下一步就是配置框架使用JDBC实现。方法还是编写`@Configuration @EnableAuthorizationServer` 类继承`AuthorizationServerConfigurerAdapter` ~~~java @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Resource private AuthenticationManager authenticationManager; @Autowired private RedisConnectionFactory connectionFactory; @Resource private UserDetailsService userDetailsService; @Autowired private DataSource dataSource; @Autowired private WebResponseExceptionTranslator webResponseExceptionTranslator; /** * 声明TokenStore实现 (redis存储) * * @return */ @Bean public RedisTokenStore redisTokenStore() { return new RedisTokenStore(connectionFactory); } @Bean public ClientDetailsService clientDetails() { return new JdbcClientDetailsService(dataSource); } ... ~~~ **三个重写方法:** ```java public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security .tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()") .allowFormAuthenticationForClients(); } } ``` ```java /** * 用来配置客户端详情服务(ClientDetailsService) * 客户端详情信息在这里进行初始化, 数据库在进行client_id 与 client_secret验证时 会使用这个service进行验证 */ public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.withClientDetails(clientDetails()); } ``` ```java /** * 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务 核心配置 在启动时就会进行配置 * 1. authenticationManager 的配置类会在后面的WebSecurityAdaptConfig配置中给出 * 2.TokenStore 一共有五种配置方式 本项目采用jdbc也就是数据库的形式 *

* 实现方式有5中, * - 1.JdbcTokenStore * - 2.RedisTokenStore * - 3.JwtTokenStore * - 4.InMemmoryTokenStore * - 5.JwkTokenStore *

* 3.WebResponseExceptionTranslator为自定义的验证错误异常返回类,其中responsedata为自定义的数据返回类 包含code,message,data三个属性 */ public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .authenticationManager(authenticationManager) .userDetailsService(userDetailsService) .tokenStore(redisTokenStore()); endpoints.exceptionTranslator(webResponseExceptionTranslator); } ``` ### 4.资源服务器 编写`@Configuration @EnableResourceServer` 类继承`ResourceServerConfigurerAdapter` ~~~java @Configuration @EnableResourceServer @Order(3) public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { super.configure(resources); } @Override public void configure(HttpSecurity http) throws Exception { http .httpBasic().and() .csrf().disable() .authorizeRequests() .anyRequest().authenticated() .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler()); } // @Override // public void configure(HttpSecurity http) throws Exception { // http. // anonymous().disable() // .requestMatchers().antMatchers("/user*/**") // .and().authorizeRequests().anyRequest().authenticated() // .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler()); // } } ~~~ ## 三、授权码模式 ### 3.1 获取code #### 3.1.1 请求url ​ http://localhost:8090/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&scope={scope}&state={state} ~~~shell http://localhost:8090/oauth/authorize?client_id=client&redirect_uri=http://www.baidu.com&response_type=code&scope=app ~~~ #### 3.1.2 请求方式 - GET #### 3.1.3 请求参数 | 参数名 | 参数值 | 是否必须 | 类型 | 说明 | | :------------ | :----- | :------- | :----- | :----------------------------------------------------------- | | client_id | | 是 | string | 应用id | | redirect_uri | | 是 | string | 回跳地址(必需和应用配置里面的地址一致) | | response_type | code | 是 | string | 返回类型 | | scope | | 否 | string | 授权范围 | | state | | 否 | string | 表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值 | #### 3.1.4 返回示例 - **登录成功后跳转回调地址redirect_uri并带上code参数** ![image-20210612174917213](security测试.assets/image-20210612174917213.png) ### 3.2 通过code获取token #### 3.2.1 请求URL http://localhost:8090/oauth/token?code={code}&grant_type=authorization_code&redirect_uri={redirect_uri}&scope={scope} ~~~shell curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=Li4NZo&redirect_uri=http://www.baidu.com' "http://client:secret@localhost:8090/oauth/token" ~~~ #### 3.2.2 请求方式 - post #### 3.2.3 请求头 | 参数名 | 参数值 | 是否必须 | 类型 | 说明 | | :------------ | :------------------------------ | :------- | :----- | :----------------------------------------------------------- | | Authorization | Basic {clientId}:{clientSecret} | 是 | string | {clientId}:{clientSecret} 的值必需使用base64加密,clientId为应用id,clientSecret为应用密钥 | ![image-20210612175352099](security测试.assets/image-20210612175352099.png) #### 3.2.4 请求参数 | 参数名 | 参数值 | 是否必须 | 类型 | 说明 | | :----------- | :----------------- | :------- | :----- | :------------------------- | | grant_type | authorization_code | 是 | string | 授权类型 | | code | | 是 | string | 第一步获取的授权码 | | redirect_uri | | 是 | string | 回调地址,必需与第一步一致 | | scope | | 否 | string | 授权范围 | #### 3.2.5 返回示例 - **正确时返回:** ``` { "access_token": "20b106d3-fd9d-437c-87c3-300ffaf6e67e", "token_type": "bearer", "refresh_token": "c4b3d67b-4311-41aa-8253-832fe39224ea", "expires_in": 16534, "scope": "app" } ``` - **错误时返回:** ``` { "datas": null, "resp_code": 1, "resp_msg": "Invalid authorization code: asPRRW" } ``` ## 四、密码模式 POST请求,http只有一步调用,如下截图 认证服务器,授予第三方应用使用哪个用户权限。 资源服务器,进行资源的鉴权 ### 4.1 请求头 | 参数名 | 参数值 | 是否必须 | 类型 | 说明 | | :------------ | :------------------------------ | :------- | :----- | :----------------------------------------------------------- | | Authorization | Basic {clientId}:{clientSecret} | 是 | string | {clientId}:{clientSecret} 的值必需使用base64加密,clientId为应用id,clientSecret为应用密钥 | ![image-20210612180039944](security测试.assets/image-20210612180039944.png) ### 4.2 请求参数 | 参数名 | 参数值 | 是否必须 | 类型 | 说明 | | :--------- | :------- | :------- | :----- | :------- | | grant_type | password | 是 | string | 授权类型 | | username | | 是 | string | 用户名 | | password | | 是 | string | 密码 | 截图如下 ![image-20210612175901143](security测试.assets/image-20210612175901143.png) ### 4.3 返回示例 - **正确时返回:** ``` { "access_token": "20b106d3-fd9d-437c-87c3-300ffaf6e67e", "token_type": "bearer", "refresh_token": "c4b3d67b-4311-41aa-8253-832fe39224ea", "expires_in": 16302, "scope": "app" } ``` ## 五、项目参考 https://github.com/yangxiufeng666/Micro-Service-Skeleton/tree/v2.0 http://www.ruoyi.vip/ https://gitee.com/y_project/RuoYi-Cloud