前言
接前面的内容,我们用 spring security 来完成开放接口平台。
授权资源服务
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>zuul-auth</artifactId>
<groupId>com.babaznkj.com</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>auth2</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.babaznkj.com</groupId>
<artifactId>common</artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- mybatis启动器 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.starter.version}</version>
</dependency>
<!-- alibaba的druid数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.starter.version}</version>
</dependency>
<!-- 不是starter,手动配置 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
</dependencies>
</project>
yml
server:
port: 8082
baba:
security:
jwt:
secret: otherpeopledontknowit
url: /login
header: Authorization
prefix: Bearer
expiration: 86400
language: CN
spring:
application:
name: backend
datasource:
name: test
url: jdbc:mysql://localhost:3306/baba_icloud_test1?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: carry0610A
# druid 连接池
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
main:
allow-bean-definition-overriding: true # 这个表示允许我们覆盖OAuth2放在容器中的bean对象,一定要配置
redis:
host: 192.168.3.119
port: 6379
password: 123456
ribbon:
ReadTimeout: 5000
SocketTimeout: 5000
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka/
instance:
prefer-ip-address: false
management:
endpoints:
security:
enabled: false
web:
exposure:
include: "*"
mybatis:
mapper-locations: classpath:mapper/*.xml # mapper映射文件位置
type-aliases-package: shuaicj.example.security.backend.entity # 实体类所在的位置
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
AuthorizationServerConfiguration.java
package com.baba.security.auth2.auth; import com.baba.security.auth2.entity.JdbcTokenStores; import com.baba.security.auth2.service.impl.MemberUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.approval.ApprovalStore; import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore; import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices; import org.springframework.security.oauth2.provider.token.TokenStore; import javax.sql.DataSource; /** * OAuth 授权服务器配置 * https://segmentfault.com/a/1190000014371789 * @author wulongbo * @date 2021-11-11 */ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { @Autowired private DataSource dataSource; @Autowired private MemberUserDetailsService userDetailsService; @Autowired private AuthenticationManager authenticationManager; @Autowired private PasswordEncoder passwordEncoder; //从数据库中查询出客户端信息 @Bean public JdbcClientDetailsService clientDetailsService() { JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource); jdbcClientDetailsService.setPasswordEncoder(passwordEncoder); return jdbcClientDetailsService; } //token保存策略 @Bean public TokenStore tokenStore() { return new JdbcTokenStores(dataSource); } //授权信息保存策略 @Bean public ApprovalStore approvalStore() { return new JdbcApprovalStore(dataSource); } //授权码模式专用对象 @Bean public AuthorizationCodeServices authorizationCodeServices() { return new JdbcAuthorizationCodeServices(dataSource); } //指定客户端登录信息来源 @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //从数据库取数据 clients.withClientDetails(clientDetailsService()); // // 从内存中取数据 // clients.inMemory() // .withClient("baidu") // .secret(passwordEncoder.encode("12345")) // .resourceIds("mayikt_resource") // .authorizedGrantTypes( // "authorization_code", // "password", // "client_credentials", // "implicit", // "refresh_token" // )// 该client允许的授权类型 authorization_code,password,refresh_token,implicit,client_credentials //// .scopes("read", "write")// 允许的授权范围 // .scopes("all")// 允许的授权范围 // .autoApprove(false) // //加上验证回调地址 // .redirectUris("http://www.baidu.com"); // // 读数据库 // clients.inMemory() // // appid // .withClient("mayikt_appid1") // .secret(passwordEncoder.encode("1234567")) // // 授权码 // .authorizedGrantTypes("authorization_code") // // 作用域 表示所有的接口都可以访问 分配我们的appid 调用接口的权限 // .scopes("all") // .resourceIds("mayikt_resource") // // 用户选择授权之后,跳转到该地址传递code授权码 // .redirectUris("http://www.mayikt.com/callback"); } //检测token的策略 @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer.allowFormAuthenticationForClients() //允许form表单客户端认证,允许客户端使用client_id和client_secret获取token // .checkTokenAccess("isAuthenticated()") //通过验证返回token信息 .checkTokenAccess("permitAll()") //打开check_token .tokenKeyAccess("permitAll()") // 获取token请求不进行拦截 .passwordEncoder(passwordEncoder); } //OAuth2的主配置信息 @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .approvalStore(approvalStore()) .authenticationManager(authenticationManager) .authorizationCodeServices(authorizationCodeServices()) .tokenStore(tokenStore()) .userDetailsService(userDetailsService); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
BaseClientDetailService.java
package com.baba.security.auth2.auth; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.ClientRegistrationException; import org.springframework.security.oauth2.provider.NoSuchClientException; import org.springframework.security.oauth2.provider.client.BaseClientDetails; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeUnit; /** * 自定义客户端认证 * @author wulongbo * @date 2021-11-11 */ public class BaseClientDetailService implements ClientDetailsService { private static final Logger log = LoggerFactory.getLogger(BaseClientDetailService.class); @Override public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException { System.out.println(clientId); BaseClientDetails client = null; //这里可以改为查询数据库 if("client".equals(clientId)) { log.info(clientId); client = new BaseClientDetails(); client.setClientId(clientId); client.setClientSecret("{noop}123456"); //client.setResourceIds(Arrays.asList("order")); client.setAuthorizedGrantTypes(Arrays.asList("authorization_code", "client_credentials", "refresh_token", "password", "implicit")); //不同的client可以通过 一个scope 对应 权限集 client.setScope(Arrays.asList("all", "select")); client.setAuthorities(AuthorityUtils.createAuthorityList("admin_role")); client.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1)); //1天 client.setRefreshTokenValiditySeconds((int)TimeUnit.DAYS.toSeconds(1)); //1天 Set<String> uris = new HashSet<>(); uris.add("http://localhost:8080/auth"); client.setRegisteredRedirectUri(uris); } if(client == null) { throw new NoSuchClientException("No client width requested id: " + clientId); } return client; } }
ResourceServerConfiguration.java
package com.baba.security.auth2.auth; import com.baba.security.auth2.entity.JdbcTokenStores; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; 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.RemoteTokenServices; import org.springframework.security.oauth2.provider.token.TokenStore; import javax.sql.DataSource; /** * OAuth 资源服务器配置 * * @author wulongbo * @date 2021-11-11 */ @Configuration @EnableResourceServer public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { @Autowired private DataSource dataSource; @Autowired private TokenStore tokenStore; /** * 指定token的持久化策略 * 其下有 RedisTokenStore保存到redis中, * JdbcTokenStore保存到数据库中, * InMemoryTokenStore保存到内存中等实现类, * 这里我们选择保存在数据库中 * * @return */ @Bean public TokenStore jdbcTokenStore() { return new JdbcTokenStores(dataSource); } @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.resourceId("mayikt_resource")//指定当前资源的id,非常重要!必须写! .tokenStore(jdbcTokenStore()); // .tokenStore(tokenStore);//指定保存token的方式 } // @Primary // @Bean // public RemoteTokenServices remoteTokenServices() { // final RemoteTokenServices tokenServices = new RemoteTokenServices(); // tokenServices.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token"); // return tokenServices; // } @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() //指定不同请求方式访问资源所需要的权限,一般查询是read,其余是write。 .antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')") .antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')") .antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')") .antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')") .antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')") .and() .headers().addHeaderWriter((request, response) -> { response.addHeader("Access-Control-Allow-Origin", "*");//允许跨域 if (request.getMethod().equals("OPTIONS")) {//如果是跨域的预检请求,则原封不动向下传达请求头信息 response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access-Control-Request-Method")); response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers")); } }); } }
SecurityConfig.java
package com.baba.security.auth2.auth; import com.baba.security.auth2.service.impl.MemberUserDetailsService; import com.baba.security.common.utils.MD5Util; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; /** * 配置我们httpBasic 登陆账号和密码 */ @Component @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MemberUserDetailsService memberUserDetailsService; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // auth. // inMemoryAuthentication() // .withUser("mayikt") // .password(passwordEncoder().encode("654321")) // .authorities("/*"); // // auth. // inMemoryAuthentication() // .withUser("baidu") // .password(passwordEncoder().encode("54321")) // .authorities("/*"); // System.out.println("==============================="); // System.out.println(passwordEncoder().encode("123456")); // System.out.println(new BCryptPasswordEncoder().encode("12345")); auth.userDetailsService(memberUserDetailsService).passwordEncoder(new PasswordEncoder() { /** * 对密码MD5 * @param rawPassword * @return */ @Override public String encode(CharSequence rawPassword) { return MD5Util.encode((String) rawPassword); } /** * rawPassword 用户输入的密码 * encodedPassword 数据库DB的密码 * @param rawPassword * @param encodedPassword * @return */ @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { String rawPass = MD5Util.encode((String) rawPassword); boolean result = rawPass.equals(encodedPassword); return result; } }); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() //所有请求都需要通过认证 .antMatchers("/auth2/**").permitAll() .and() .httpBasic() //Basic登录 .and() .csrf().disable(); //关跨域保护 } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
ProductController.java
package com.baba.security.auth2.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @Author wulongbo * @Date 2021/11/11 19:10 * @Version 1.0 */ @RestController @RequestMapping("/product") public class ProductController { @RequestMapping("/findAll") public String findAll() { return "授权成功"; } @RequestMapping("/findAll1") public String findAll1() { return "授权成功1"; } }
- dao和service以及mapper.xml参考前面的就好了
JdbcTokenStores.java
package com.baba.security.auth2.entity; import com.alibaba.druid.support.logging.Log; import com.alibaba.druid.support.logging.LogFactory; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; import javax.sql.DataSource; /** * @Author wulongbo * @Date 2021/11/11 19:56 * @Version 1.0 */ public class JdbcTokenStores extends JdbcTokenStore { private static final Log LOG = LogFactory.getLog(JdbcTokenStores.class); public JdbcTokenStores(DataSource dataSource) { super(dataSource); } @Override public OAuth2AccessToken readAccessToken(String tokenValue) { OAuth2AccessToken accessToken = null; try { accessToken = new DefaultOAuth2AccessToken(tokenValue); } catch (EmptyResultDataAccessException e) { if (LOG.isInfoEnabled()) { LOG.info("Failed to find access token for token "+tokenValue); } } catch (IllegalArgumentException e) { LOG.warn("Failed to deserialize access token for " +tokenValue,e); removeAccessToken(tokenValue); } return accessToken; } }
Application.java
package com.baba.security.auth2; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Spring boot app. * * @author wulongbo 2021/10/18 */ @SpringBootApplication @MapperScan("com.baba.security.auth2.dao") public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
- 数据库
INSERT INTO `baba_icloud_test1`.`oauth_client_details` (`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('baidu', 'mayikt_resource,user,device', '$2a$10$32N/ptu3jY0WjFH0qLbcEO2ZcCg4gYCJvMbwmqzf84qNCcDFBLl4q', 'read,write', 'authorization_code', 'http://www.mayikt.com/callback', NULL, NULL, NULL, NULL, NULL);
INSERT INTO `baba_icloud_test1`.`oauth_client_details` (`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('mayikt_appid', 'mayikt_resource', '$2a$10$6/Sab9Au.CdKqyE4x0gr0OJZrzrcDCWZ9GLqMDF6KX.jHad5vlkeO', 'read,write,trust', 'authorization_code', 'http://www.mayikt.com/callback', NULL, NULL, NULL, NULL, NULL);
- 测试
访问
获取到code授权码
根据授权码获取access_token
http://localhost:8082/oauth/token?code=StPCWn&grant_type=authorization_code&redirect_uri=http://www.mayikt.com/callback&Scope=all&client_id=mayikt_appid&client_secret=123456
当然也可以通过网关来访问:
http://localhost:8080/auth2/oauth/token?code=n6d6hP&grant_type=authorization_code&redirect_uri=http://www.mayikt.com/callback&Scope=all&client_id=mayikt_appid&client_secret=123456
检查access_token
http://localhost:8082/oauth/check_token?token=5c469473-8599-44f2-9061-fbe61be37ff5
调用接口http://localhost:8080/auth2/product/findAll主装Bearer
拼接token放入header中请求接口
当然也可以不组装直接请求
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。