OAuth(开发授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。
OAuth2.0是OAuth的延续,但他并不兼容OAuth1.0,即它完全废除OAuth1.0。很多大公司如Google,Yahoo,Microsoft等都提供了OAuth认证服 务,这些都足以说明OAUTH标准逐渐成为开放资源授权的标准。
微信小程序授权登录同样也是提供OAuth认证服务
当需要访问第三方系统的资源时需要首先通过第三方系统的认证(例如:微信认证),由第三方系统对用户认证通过,并授权资源的访问权限。
1,客户端:本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源。
2,资源拥有者:通常为用户,也可以是应用程序,即该资源的拥有者。
3,授权服务器(认证服务器):用于服务提供商对资源拥有者的身份认证,对访问资源进行授权,认证成功后会给客户端发放令牌(access_token),作为客户端访问资源服务器的凭据。
4,资源服务器:存储资源的服务器
5,客户端标识:client_id
6,客户端密钥:client_secret
为什么要有client_id和client_secret呢?
因为服务提供商不能允许随便一个客户端就接入到它的授权服务器,服务提供商会 给准入的接入方一个身份,用于接入时的凭据。
shop-oauth-service 认证服务(颁发Token)
shop-gateway-service 网关(认证用户的请求 也即认证Token)
shop-service 服务(资源服务)
1.父模块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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
</parent>
<groupId>com.lpz</groupId>
<artifactId>shop-parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>shop-server</module>
<module>shop-api</module>
<module>shop-gateway-server</module>
<module>shop-oauth-server</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2.网关模块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>shop-parent</artifactId>
<groupId>com.lpz</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shop-gateway-server</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
3.shop-server模块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>shop-parent</artifactId>
<groupId>com.lpz</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shop-server</artifactId>
<packaging>pom</packaging>
<modules>
<module>shop-user-server</module>
<module>shop-goods-server</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 服务注册/发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
</dependencies>
</project>
4.shop-oauth-server模块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>shop-parent</artifactId>
<groupId>com.lpz</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shop-oauth-server</artifactId>
<dependencies>
<!-- 服务注册/发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-data</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.lpz</groupId>
<artifactId>shop-user-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
shop-oauth-server yml配置文件
server:
port: 1100
spring:
application:
name: user-auth
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/shop_oauth?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=UTC
username: root
password: abc123
main:
allow-bean-definition-overriding: true
cloud:
nacos:
discovery:
server-addr: 192.168.56.1:8848
auth:
ttl: 3600 #token存储到redis的过期时间
clientId: accp
clientSecret: accp
cookieDomain: localhost
cookieMaxAge: -1
encrypt:
key-store:
location: classpath:/wfshop.jks
secret: wfshop
alias: wfshop
password: wfshop
AuthorizationServerConfig配置类
package com.lpz.oauth.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.security.KeyPair;
@Configuration
@EnableAuthorizationServer
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
//数据源,用于从数据库获取数据进行认证操作,测试可以从内存中获取
@Autowired
private DataSource dataSource;
//jwt令牌转换器
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
//SpringSecurity 用户自定义授权认证类
@Autowired
UserDetailsService userDetailsService;
//授权认证管理器
@Autowired
AuthenticationManager authenticationManager;
//令牌持久化存储接口
@Autowired
TokenStore tokenStore;
@Autowired
private CustomUserAuthenticationConverter customUserAuthenticationConverter;
/***
* 客户端信息配置
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource).clients(clientDetails());
// clients.inMemory()
// .withClient("changgou") //客户端id
// .secret("changgou") //秘钥
// .redirectUris("http://localhost") //重定向地址
// .accessTokenValiditySeconds(3600) //访问令牌有效期
// .refreshTokenValiditySeconds(3600) //刷新令牌有效期
// .authorizedGrantTypes(
// "authorization_code", //根据授权码生成令牌
// "client_credentials", //客户端认证
// "refresh_token", //刷新令牌
// "password") //密码方式认证
// .scopes("app"); //客户端范围,名称自定义,必填
}
/***
* 授权服务器端点配置
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.accessTokenConverter(jwtAccessTokenConverter)
.authenticationManager(authenticationManager)//认证管理器
.tokenStore(tokenStore) //令牌存储
.userDetailsService(userDetailsService); //用户信息service
}
/***
* 授权服务器的安全配置
* @param oauthServer
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.allowFormAuthenticationForClients()
.passwordEncoder(new BCryptPasswordEncoder())
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
//读取密钥的配置
@Bean("keyProp")
public KeyProperties keyProperties(){
return new KeyProperties();
}
@Resource(name = "keyProp")
private KeyProperties keyProperties;
//客户端配置
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Bean
@Autowired
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
/****
* JWT令牌转换器
* @param customUserAuthenticationConverter
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(CustomUserAuthenticationConverter customUserAuthenticationConverter) {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory(
keyProperties.getKeyStore().getLocation(), //证书路径 changgou.jks
keyProperties.getKeyStore().getSecret().toCharArray()) //证书秘钥 changgouapp
.getKeyPair(
keyProperties.getKeyStore().getAlias(), //证书别名 changgou
keyProperties.getKeyStore().getPassword().toCharArray()); //证书密码 changgou
converter.setKeyPair(keyPair);
//配置自定义的CustomUserAuthenticationConverter
DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter();
accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter);
return converter;
}
}
CustomUserAuthenticationConverter类
package com.lpz.oauth.config;
import com.lpz.oauth.util.UserJwt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
@Component
public class CustomUserAuthenticationConverter extends DefaultUserAuthenticationConverter {
@Autowired
UserDetailsService userDetailsService;
@Override
public Map<String, ?> convertUserAuthentication(Authentication authentication) {
LinkedHashMap response = new LinkedHashMap();
String name = authentication.getName();
response.put("username", name);
Object principal = authentication.getPrincipal();
UserJwt userJwt = null;
if(principal instanceof UserJwt){
userJwt = (UserJwt) principal;
}else{
//refresh_token默认不去调用userdetailService获取用户信息,这里我们手动去调用,得到 UserJwt
UserDetails userDetails = userDetailsService.loadUserByUsername(name);
userJwt = (UserJwt) userDetails;
}
response.put("name", userJwt.getName());
response.put("id", userJwt.getId());
//公司 response.put("compy", "songsi");
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}
return response;
}
}
UserDetailsServiceImpl实现类
package com.lpz.oauth.config;
import com.lpz.dto.TbUserDto;
import com.lpz.feign.UserFeignService;
import com.lpz.oauth.util.UserJwt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
/*****
* 自定义授权认证类
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
ClientDetailsService clientDetailsService;
@Autowired
UserFeignService userFeignService;
/****
* 自定义授权认证
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//取出身份,如果身份为空说明没有认证
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//没有认证统一采用httpbasic认证,httpbasic中存储了client_id和client_secret,开始认证client_id和client_secret
if (authentication == null) {
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(username);
if (clientDetails != null) {
//秘钥
String clientSecret = clientDetails.getClientSecret();
//静态方式
// return new User(username,new BCryptPasswordEncoder().encode(clientSecret), AuthorityUtils.commaSeparatedStringToAuthorityList(""));
//数据库查找方式
return new User(username, clientSecret, AuthorityUtils.commaSeparatedStringToAuthorityList(""));
}
}
if (StringUtils.isEmpty(username)) {
return null;
}
//根据用户名查询用户信息
TbUserDto info = userFeignService.info(username);
String pwd = info.getPassword();
//创建User对象
String permissions = "goods_list,seckill_list";
UserJwt userDetails = new UserJwt(username, pwd, AuthorityUtils.commaSeparatedStringToAuthorityList(permissions));
//userDetails.setComy(songsi);
return userDetails;
}
}
WebSecurityConfig配置类
package com.lpz.oauth.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.builders.WebSecurity;
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;
@Configuration
@EnableWebSecurity
@Order(-1)
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/***
* 忽略安全拦截的URL
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/oauth/login",
"/user/logout");
}
/***
* 创建授权管理认证对象
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
/***
* 采用BCryptPasswordEncoder对密码进行编码
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/****
*
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.httpBasic() //启用Http基本身份验证
.and()
.formLogin() //启用表单身份验证
.and()
.authorizeRequests() //限制基于Request请求访问
.anyRequest()
.authenticated(); //其他请求都需要经过验证
}
}
AuthToken工具类
package com.lpz.oauth.util;
import java.io.Serializable;
public class AuthToken implements Serializable{
//令牌信息
String accessToken;
//刷新token(refresh_token)
String refreshToken;
//jwt短令牌
String jti;
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public String getJti() {
return jti;
}
public void setJti(String jti) {
this.jti = jti;
}
}
UserJwt工具类
package com.lpz.oauth.util;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
public class UserJwt extends User {
private String id; //用户ID
private String name; //用户名字
private String comny;//设置公司
public UserJwt(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
ResourceServerConfig(资源服务器上的配置类)
package com.lpz.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//激活方法上的PreAuthorize注解
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//公钥
private static final String PUBLIC_KEY = "public.key";
/***
* 定义JwtTokenStore
* @param jwtAccessTokenConverter
* @return
*/
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
/***
* 定义JJwtAccessTokenConverter
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(getPubKey());
return converter;
}
/**
* 获取非对称加密公钥 Key
*
* @return 公钥 Key
*/
private String getPubKey() {
Resource resource = new ClassPathResource(PUBLIC_KEY);
try {
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
BufferedReader br = new BufferedReader(inputStreamReader);
return br.lines().collect(Collectors.joining("\n"));
} catch (IOException ioe) {
return null;
}
}
/***
* Http安全配置,对每个到达系统的http请求链接进行校验
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
//所有请求必须认证通过
http.authorizeRequests()
//下边的路径放行
.antMatchers(
"/tbuser/info/**"). //配置地址放行
permitAll()
.anyRequest().
authenticated(); //其他地址需要认证授权
}
}
gateway-server(全局过滤器代码)
package com.lpz.filters;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class PowerFilters implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String path = request.getURI().getPath();
if (path.startsWith("/api/oauth/login")) {
return chain.filter(exchange);
}
String token = request.getQueryParams().getFirst("token");
if (StringUtils.isEmpty(token)) {
token = request.getHeaders().getFirst("token");
if (StringUtils.isEmpty(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
}
request.mutate().header("Authorization", "bearer " + token);
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
shop-oauth-server(AuthServiceImpl代码)
package com.lpz.oauth.service.impl;
import com.lpz.oauth.service.AuthService;
import com.lpz.oauth.util.AuthToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.util.Base64;
import java.util.List;
import java.util.Map;
@Service
public class AuthServiceImpl implements AuthService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@Override
public AuthToken login(String username, String password, String clientId, String clientSecret) {
List<ServiceInstance> instances = discoveryClient.getInstances("oauth-server");
ServiceInstance serviceInstance = instances.get(0);
String url = "http://" + serviceInstance.getHost()+":" + serviceInstance.getPort() + "/oauth/token";
LinkedMultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "password");
body.add("username", username);
body.add("password", password);
LinkedMultiValueMap<String, String> head = new LinkedMultiValueMap<>();
head.add("Authorization", myEncoder(clientId, clientSecret));
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(body, head), Map.class);
Map map = response.getBody();
System.out.println(map);
AuthToken authToken = new AuthToken();
authToken.setAccessToken(map.get("access_token").toString());
authToken.setJti(map.get("jti").toString());
authToken.setRefreshToken(map.get("refresh_token").toString());
return authToken;
}
private static String myEncoder(String clientId, String clientSecret) {
String code = clientId + ":" + clientId;
byte[] encode = Base64Utils.encode(code.getBytes());
return "Basic " + new String(encode);
}
public static void main(String[] args) {
String s = myEncoder("changgou", "changgou");
System.out.println(s);
}
}
上图的业务流程如下:
客户端请求认证服务申请令牌
认证服务生成令牌认证服务采用非对称加密算法,使用私钥生成令牌。
客户端携带令牌访问资源服务客户端在Http header 中添加: Authorization:Bearer 令牌。
资源服务请求认证服务校验令牌的有效性资源服务接收到令牌,使用公钥校验令牌的合法性。
令牌有效,资源服务向客户端响应资源信息
在对称加密的时代,加密和解密用的是同一个密钥,这个密钥既用于加密,又用于解密。这样做有一个明显的缺点,如果两个人之间传输文件,两个人都要知道密钥,如果是三个人呢,五个人呢?于是就产生了非对称加密,用一个密钥进行加密(公钥),用另一个密钥进行解密(私钥)。
总结:公钥加密、私钥解密、私钥签名、公钥验签。
Spring Security 提供对JWT的支持,本节我们使用Spring Security 提供的JwtHelper来创建JWT令牌,校验JWT令牌 等操作。 这里JWT令牌我们采用非对称算法进行加密,所以我们要先生成公钥和私钥。
(1)生成密钥证书 下边命令生成密钥证书,采用RSA 算法每个证书包含公钥和私钥
创建一个文件夹,在该文件夹下执行如下命令行:
keytool -genkeypair -alias qianggou -keyalg RSA -keypass qianggou -keystore qianggou.jks -storepass qianggou
Keytool 是一个java提供的证书管理工具
-alias:密钥的别名
-keyalg:使用的hash算法
-keypass:密钥的访问密码
-keystore:密钥库文件名,qianggou.keystore保存了生成的证书
-storepass:密钥库的访问密码
查询证书信息
keytool -list -keystore qianggou.jks
删除别名
keytool -delete -alias qianggou -keystore qianggou.jsk
openssl是一个加解密工具包,这里使用openssl来导出公钥信息。
安装 openssl:
配置openssl的path环境变量,如下图:
本教程配置在C:\OpenSSL-Win64\bin
keytool -list -rfc --keystore qianggou.jks | openssl x509 -inform pem -pubkey
下面段内容是公钥
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhTN8iyCyQEZPUmLw0GB0
rH0xeT23w4c8X8thfEtphIBTAKOoUKHxhzBHra/WHLpPw/fV/m9KTvbWCdlBziKW
r4P//KtUoa5D3qJupsPDxl9GTXJjG/9y/E+9hHLtl+cM2Qu+RTayFQ4X4ZqLjiOc
lZZjN8g3MUomu45ab7PVJvuNUGcMq8s+pSvIwHQejYVKlPH8amSqyyxEyzH2MFBX
N1c0KoerMfaDV0uzFCsUdOxUlpow9byCPZAspuNbN3DG3xwoHtOErWcchs8TlGMy
So2QAeGADmCkH/iIHbF22ytmybIsbK5Ww9T+a1xK+wzzKBAucL+HuK5hLwQlSyuq
zQIDAQAB
-----END PUBLIC KEY-----
将上边的公钥拷贝到文本public.key文件中,合并为一行,可以将它放到需要实现授权认证的工程中。
在运行之前我们再来梳理一下流程