直接上代码:
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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <artifactId>b8</artifactId> <groupId>com.pmpg</groupId> <version>1.0.0</version> </parent> <groupId>com.pmpg</groupId> <artifactId>b8-auth</artifactId> <version>0.0.1-SNAPSHOT</version> <name>b8-auth</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--nacos 注册中心 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> <!-- 缓存配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- spring security oauth2--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <!-- openfeign 服务远程调用 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>com.pmpg</groupId> <artifactId>b8-common-entity</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>com.pmpg</groupId> <artifactId>b8-common-redis</artifactId> <version>1.0.0</version> <scope>compile</scope> </dependency> <!-- 日志--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.10</version> </dependency> </dependencies> <build> <finalName>b8-auth</finalName> <plugins> <!-- 打包生成fat jar --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.4.RELEASE</version> <configuration> <mainClass>com.b8.auth.B8AuthApplication</mainClass> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> <excludes> <exclude>**/*.jks</exclude> </excludes> </resource> <resource> <directory>src/main/resources</directory> <filtering>false</filtering> <includes> <include>**/*.jks</include> </includes> </resource> </resources> </build> </project>
AuthorizationServerConfig
package com.b8.auth.config; import com.b8.auth.enhancer.B8AuthTokenEnhancer; import com.b8.auth.service.B8AuthUserDetailService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; 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.TokenEnhancer; import org.springframework.security.oauth2.provider.token.TokenEnhancerChain; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import javax.sql.DataSource; import java.util.ArrayList; import java.util.List; /** * @Description 基于DB模式配置授权服务器存储第三方客户端的信息 * @Author zhiwei Liao * @Date 2021/8/17 15:42 **/ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private DataSource dataSource; @Autowired @Qualifier("jwtTokenStore") private TokenStore tokenStore; @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; @Autowired private B8AuthUserDetailService userDetailService; @Autowired private AuthenticationManager authenticationManagerBean; @Autowired private B8AuthTokenEnhancer b8AuthTokenEnhancer; /** * @Description 第三方信息的存储 * @MethodParameterTypes [org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer] * @MethodParameters [clients] * @MethodReturnType void * @Author zhiwei Liao * @Date 2021/8/17 14:57 **/ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 第三方信息的存储 基于jdbc clients.withClientDetails(clientDetailsService()); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { //配置JWT的内容增强器 TokenEnhancerChain enhancerChain = new TokenEnhancerChain(); List<TokenEnhancer> delegates = new ArrayList<>(); delegates.add(b8AuthTokenEnhancer); delegates.add(jwtAccessTokenConverter); enhancerChain.setTokenEnhancers(delegates); //使用密码模式需要配置 endpoints.authenticationManager(authenticationManagerBean) .reuseRefreshTokens(false) //refresh_token是否重复使用 .userDetailsService(userDetailService) //刷新令牌授权包含对用户信息的检查 .tokenStore(new JdbcTokenStore(dataSource)) //指定token存储策略是jwt,存储到mysql .accessTokenConverter(jwtAccessTokenConverter) .tokenEnhancer(enhancerChain) //配置tokenEnhancer .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求 } @Bean public JdbcTokenStore jdbcTokenStore(){ return new JdbcTokenStore(dataSource); } /** * 授权服务器安全配置 * @param security * @throws Exception */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { //第三方客户端校验token需要带入 clientId 和clientSecret来校验 security.checkTokenAccess("isAuthenticated()") .tokenKeyAccess("isAuthenticated()");//来获取我们的tokenKey需要带入clientId,clientSecret //允许表单认证 security.allowFormAuthenticationForClients(); } @Bean public ClientDetailsService clientDetailsService(){ return new JdbcClientDetailsService(dataSource); } }
AuthResourceServerConfig
package com.b8.auth.config; import org.springframework.context.annotation.Configuration; 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; /** * 资源服务配置 */ @Configuration @EnableResourceServer public class AuthResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest().authenticated(); } }
JwtTokenStoreConfig
package com.b8.auth.config; import com.b8.auth.enhancer.B8AuthTokenEnhancer; import com.b8.auth.properties.JwtCAProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; 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.rsa.crypto.KeyStoreKeyFactory; import java.security.KeyPair; @Configuration @EnableConfigurationProperties(value = JwtCAProperties.class) public class JwtTokenStoreConfig { @Bean public TokenStore jwtTokenStore(){ return new JwtTokenStore(jwtAccessTokenConverter()); } @Bean public B8AuthTokenEnhancer b8AuthTokenEnhancer() { return new B8AuthTokenEnhancer(); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter(); //配置JWT使用的秘钥 非对称加密 accessTokenConverter.setKeyPair(keyPair()); return accessTokenConverter; } @Autowired private JwtCAProperties jwtCAProperties; @Bean public KeyPair keyPair() { KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource(jwtCAProperties.getKeyPairName()), jwtCAProperties.getKeyPairSecret().toCharArray()); return keyStoreKeyFactory.getKeyPair(jwtCAProperties.getKeyPairAlias(), jwtCAProperties.getKeyPairStoreSecret().toCharArray()); } }
RedisConfig
package com.b8.auth.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; /** * @author zhiwei Liao */ @Configuration public class RedisConfig { @Autowired private RedisConnectionFactory redisConnectionFactory; @Bean public TokenStore tokenStore(){ // access_token return new RedisTokenStore(redisConnectionFactory); } }
WebSecurityConfig
package com.b8.auth.config; import com.b8.auth.service.B8AuthUserDetailService; 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.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; /** * @Description 配置SpringSecurity,“将Spring Security与Spring Gateway一起使用时出现无法访问javax.servlet.Filter”错误”, * 把Spring Gateway和Spring Security放在一起,因为我想保护我的网关。但是在实现了以下扩展WebSecurityConfigurerAdapter的类之后, * 项目抛出java:无法访问javax.servlet.Filter * 从Spring Cloud Gateway文档中:Spring Cloud Gateway需要Spring Boot和Spring Webflux提供的Netty运行时。 * 它不能在传统的Servlet容器中工作,也不能在构建为WAR时工作。扩展WebSecurityConfigurerAdapter是为了基于servlet的应用程序 * @Author zhiwei Liao * @Date 2021/8/17 15:44 **/ @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private B8AuthUserDetailService userDetailService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailService); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } /** * @Description 密码模式 * @MethodReturnType org.springframework.security.crypto.password.PasswordEncoder * @Author zhiwei Liao * @Date 2021/8/17 15:46 **/ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin().permitAll() .and().authorizeRequests() .antMatchers("/oauth/**").permitAll()//不拦截 .anyRequest() .authenticated() .and().logout().permitAll()//退出放行 .and().csrf().disable(); } }
UserinfoDetails
package com.b8.auth.domain; import com.common.entity.po.B8UserUserinfoEntity; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Arrays; import java.util.Collection; /** * @author zhiwei Liao */ public class UserinfoDetails implements UserDetails { private B8UserUserinfoEntity userUserinfo; public UserinfoDetails(B8UserUserinfoEntity userUserinfo) { this.userUserinfo = userUserinfo; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { //返回当前用户的权限 BRAC user role authority return Arrays.asList(new SimpleGrantedAuthority("TEST")); } // 获取用户密码(凭证) @Override public String getPassword() { return userUserinfo.getCredential(); } // 获取用户名 @Override public String getUsername() { return userUserinfo.getNickNameId(); } // 判断帐号是否已经过期 @Override public boolean isAccountNonExpired() { return true; } // 判断帐号是否已被锁定 @Override public boolean isAccountNonLocked() { return true; } // 判断用户凭证是否已经过期 @Override public boolean isCredentialsNonExpired() { return true; } // 判断用户帐号是否已启用 @Override public boolean isEnabled() { return !userUserinfo.getUserStatus().equals("FREEZE"); } public B8UserUserinfoEntity getUserUserinfo() { return userUserinfo; } }
B8AuthTokenEnhancer
package com.b8.auth.enhancer; import com.b8.auth.domain.UserinfoDetails; import com.common.entity.po.B8UserUserinfoEntity; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.token.TokenEnhancer; import java.util.HashMap; import java.util.Map; public class B8AuthTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { UserinfoDetails userinfoDetails = (UserinfoDetails) authentication.getPrincipal(); final Map<String, Object> additionalInfo = new HashMap<>(); final Map<String, Object> retMap = new HashMap<>(); //todo 这里暴露userId到Jwt的令牌中,后期可以根据自己的业务需要 进行添加字段 additionalInfo.put("userId",userinfoDetails.getUserUserinfo().getId()); additionalInfo.put("userName",userinfoDetails.getUserUserinfo().getNickNameId()); additionalInfo.put("nickName",userinfoDetails.getUserUserinfo().getDisplayName()); additionalInfo.put("loginType",userinfoDetails.getUserUserinfo().getLoginType()); retMap.put("additionalInfo",additionalInfo); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(retMap); return accessToken; } }
JwtCAProperties
package com.b8.auth.properties; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; /** * @Description 读取配置文件中的属性配置 * @Author zhiwei Liao * @Date 2021/8/18 10:04 **/ @Data @ConfigurationProperties(prefix = "b8auth.jwt") public class JwtCAProperties { /** * 证书名称 */ private String keyPairName; /** * 证书别名 */ private String keyPairAlias; /** * 证书私钥 */ private String keyPairSecret; /** * 证书存储密钥 */ private String keyPairStoreSecret; }
UserServiceHystrix
package com.b8.auth.service.impl; import com.b8.auth.api.ResultData; import com.b8.auth.service.UserInfoFeignService; import com.common.entity.po.B8UserUserinfoEntity; import org.springframework.stereotype.Component; /** * @author zhiwei Liao * @version 1.0 * @Description * @Date 2021/8/17 15:25 */ @Component public class UserServiceHystrix implements UserInfoFeignService { @Override public ResultData<B8UserUserinfoEntity> getUserinfoById(String userId) { return null; } @Override public ResultData<B8UserUserinfoEntity> getUserByUsername(String username) { return null; } }
B8AuthUserDetailService
package com.b8.auth.service; import com.b8.auth.api.ResultData; import com.b8.auth.domain.UserinfoDetails; import com.common.entity.po.B8UserUserinfoEntity; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; /** * @author zhiwei Liao * @version 1.0 * @Description * @Date 2021/8/17 15:02 */ @Service @Slf4j public class B8AuthUserDetailService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // TODO 查数据库获取用户信息 rpc调用 // 加载用户信息 if (StringUtils.isEmpty(username)) { log.warn("用户登陆用户名为空:{}", username); throw new UsernameNotFoundException("用户名不能为空"); } B8UserUserinfoEntity userUserinfo = getByUsername(username); if (null == userUserinfo) { log.warn("根据用户名没有查询到对应的用户信息:{}", username); } log.info("根据用户名:{}获取用户登陆信息:{}", username, userUserinfo); // 用户信息的封装 implements UserDetails UserinfoDetails memberDetails = new UserinfoDetails(userUserinfo); return memberDetails; } @Autowired private UserInfoFeignService userInfoFeignService; public B8UserUserinfoEntity getByUsername(String username) { // fegin获取用户信息 ResultData<B8UserUserinfoEntity> resultData = userInfoFeignService.getUserByUsername(username); return resultData.getData(); } }
UserInfoFeignService
package com.b8.auth.service; import com.b8.auth.api.ResultData; import com.b8.auth.service.impl.UserServiceHystrix; import com.common.entity.po.B8UserUserinfoEntity; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; /** * @author zhiwei Liao * @version 1.0 * @Description * @Date 2021/8/17 15:24 */ @Component @FeignClient(name = "user", fallback = UserServiceHystrix.class, path = "/user") public interface UserInfoFeignService { @GetMapping("/getUserinfoById") ResultData<B8UserUserinfoEntity> getUserinfoById(@RequestParam("userId") String userId); @GetMapping("/getUserByUsername") ResultData<B8UserUserinfoEntity> getUserByUsername(@RequestParam("username") String username); }
IErrorCode
package com.b8.auth.api; /** * 封装API的错误码 */ public interface IErrorCode { int getCode(); String getMessage(); }
ResultCode
package com.b8.auth.api; /** * 枚举了一些常用API操作码 */ public enum ResultCode implements IErrorCode { SUCCESS(200, "操作成功"), FAILED(500, "操作失败"), VALIDATE_FAILED(404, "参数检验失败"), UNAUTHORIZED(401, "暂未登录或token已经过期"), AUTHORIZATION_HEADER_IS_EMPTY(600,"请求头中的token为空"), GET_TOKEN_KEY_ERROR(601,"远程获取TokenKey异常"), GEN_PUBLIC_KEY_ERROR(602,"生成公钥异常"), JWT_TOKEN_EXPIRE(603,"token校验异常"), TOMANY_REQUEST_ERROR(429,"后端服务触发流控"), BACKGROUD_DEGRADE_ERROR(604,"后端服务触发降级"), BAD_GATEWAY(502,"网关服务异常"), FORBIDDEN(403, "没有相关权限"); private int code; private String message; private ResultCode(int code, String message) { this.code = code; this.message = message; } public int getCode() { return code; } public String getMessage() { return message; } }
ResultData
package com.b8.auth.api; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @Data @NoArgsConstructor public class ResultData<T> implements Serializable { /** * 状态码 */ public boolean status = true; /** * 状态码 */ private Integer code = 200; /** * 接口返回信息 */ private String msg; /** * 数据对象 */ private T data; /** * 初始化一个新创建的 ResultData 对象 * * @param status 状态码 * @param msg 返回内容 */ public ResultData(Boolean status, String msg) { this.status = status; this.msg = msg; } /** * 初始化一个新创建的 ResultData 对象 * * @param status 状态码 * @param msg 返回内容 * @param data 数据对象 */ public ResultData(Boolean status, String msg, T data, Integer code) { this.status = status; this.msg = msg; this.data = data; this.code = code; } public ResultData(T data) { this.data = data; } /** * 返回成功消息 * * @param msg 返回内容 * @param data 数据对象 * @return 成功消息 */ public static <T> ResultData<T> success(String msg, T data) { return new ResultData<T>(true, msg, data, 200); } /** * 返回成功消息 * * @param msg 返回内容 * @return 成功消息 */ public static <T> ResultData<T> success(String msg) { return ResultData.success(msg, null); } /** * 返回成功消息 * * @return 成功消息 */ public static <T> ResultData<T> success() { return ResultData.success(null); } /** * 返回成功数据 * * @return 成功消息 */ public static <T> ResultData<T> success(T data) { return ResultData.success(null, data); } /** * 返回错误消息 * * @return */ public static <T> ResultData<T> error() { return ResultData.error(null); } /** * 返回错误消息 * * @param msg 返回内容 * @return 警告消息 */ public static <T> ResultData<T> error(String msg) { return ResultData.error(msg, null); } /** * 返回错误消息 * * @param code 状态码 * @param msg 返回内容 * @return 警告消息 */ public static <T> ResultData<T> error(Integer code, String msg) { return new ResultData<T>(false, msg, null, code); } /** * 返回错误消息 * * @param msg 返回内容 * @param data 数据对象 * @return 警告消息 */ public static <T> ResultData<T> error(String msg, T data) { return new ResultData<T>(false, msg, data, 500); } }
B8AuthApplication
package com.b8.auth; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableFeignClients public class B8AuthApplication { public static void main(String[] args) { SpringApplication.run(B8AuthApplication.class, args); System.out.println("=======授权模块启动成功==========="); } }
bootstrap.yml
# Tomcat server: port: 10000 tomcat: basedir: /tmp/tomcat # Spring spring: application: name: auth profiles: # 环境配置 active: local messages: encoding: UTF-8 basename: i18n/messages main: allow-bean-definition-overriding: true ##hystrix的超时时间 hystrix: command: default: execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 60000 ##Ribbon超时重试配置 ribbon: ConnectTimeout: 10000 #毫秒 连接超时时间 ReadTimeout: 600000 #毫秒 逻辑处理超时时间 OkToRetryOnAllOperations: true # 是否对所有操作都进行重试 MaxAutoRetries: 3 # 对当前实例的最大重试次数(请求服务超时6s则会再请求一次) MaxAutoRetriesNextServer: 1 # 切换实例的最大重试次数(如果还失败就切换下 b8auth: jwt: keyPairName: jwt.jks keyPairAlias: jwt keyPairSecret: 123456 keyPairStoreSecret: 123456
这里就已经搭建好了一套权限框架了,这里一般和网关配套使用,由网关获取请求头里的参数,进行校验access_token是否一致,判断当前请求是否合法。