赞
踩
前置知识:
jwt (也就是这里用到的token)
spring security
oauth2协议
采用的认证授权方案大致流程为:
介绍:
<!-- spring cloud --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR2</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> <!-- 已包含spring security + spring security ouath2 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <!--jdbc--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!--mysql--> <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>
一般需要这几张表:
http://www.andaily.com/spring-oauth-server/db_table_description.html
创建表sql脚本:
-- 客户端应用注册详情 create table oauth_client_details ( client_id VARCHAR(256) PRIMARY KEY, -- 客户端应用的账号 resource_ids VARCHAR(256), -- 客户端应用可访问的资源服务器列表,(空代表所有资源服务器都可以访问) client_secret VARCHAR(256), -- 客户端应用的密码 scope VARCHAR(256), -- 资源服务器拥有的所有权限列表 (get add delete update) authorized_grant_types VARCHAR(256), -- 客户端支持的授权码模式列表 web_server_redirect_uri VARCHAR(256), -- 授权码模式,申请授权码后重定向的uri. authorities VARCHAR(256), access_token_validity INTEGER, -- 设置颁发token的有效期 refresh_token_validity INTEGER, -- 颁发refresh_token的有效期(不设置不会同时颁发refresh_token) additional_information VARCHAR(4096), autoapprove VARCHAR(256) -- 设置为true,授权码模式下自动授权 ); create table oauth_client_token ( token_id VARCHAR(256), token BLOB, authentication_id VARCHAR(256) PRIMARY KEY, user_name VARCHAR(256), client_id VARCHAR(256) ); -- 基于session认证时,存放颁发的token create table oauth_access_token ( token_id VARCHAR(256), token BLOB, authentication_id VARCHAR(256) PRIMARY KEY, user_name VARCHAR(256), client_id VARCHAR(256), authentication BLOB, refresh_token VARCHAR(256) ); create table oauth_refresh_token ( token_id VARCHAR(256), token BLOB, authentication BLOB ); -- 授权码模式下,存放颁发的授权码 create table oauth_code ( code VARCHAR(256), authentication BLOB ); create table oauth_approvals ( userId VARCHAR(256), clientId VARCHAR(256), scope VARCHAR(256), status VARCHAR(10), expiresAt DATETIME, lastModifiedAt DATETIME ); -- 基于session认证时,并且采用分布式session CREATE TABLE SPRING_SESSION ( PRIMARY_ID CHAR(36) NOT NULL, SESSION_ID CHAR(36) NOT NULL, CREATION_TIME BIGINT NOT NULL, LAST_ACCESS_TIME BIGINT NOT NULL, MAX_INACTIVE_INTERVAL INT NOT NULL, EXPIRY_TIME BIGINT NOT NULL, PRINCIPAL_NAME VARCHAR(100), CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID) ); CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID); CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME); CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME); CREATE TABLE SPRING_SESSION_ATTRIBUTES ( SESSION_PRIMARY_ID CHAR(36) NOT NULL, ATTRIBUTE_NAME VARCHAR(200) NOT NULL, ATTRIBUTE_BYTES BLOB NOT NULL, CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME), CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE ); CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_PRIMARY_ID);
在oauth_client_details表配置client客户端信息
INSERT INTO `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 ('burukeyou-user', NULL, '$2a$10$Qg.q5o1ZBVy/7TrV107u6ORr1Vyl.wm8AGU5tFcbpw2zPQuuxh82C', 'get,add', 'password,authorization_code,refresh_token', NULL, NULL, 60000, 300, NULL, NULL);
省略(对实现最终效果影响不大):
@Component @Slf4j public class Ouath2UserDetailService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Assert.notNull(username,"aut username can not be null"); // (省略.......)创建UserTokenVo,它就是 要继承UserDetails接口,添加稍后要存放到token里的字段到这个类(稍后自定义实现token增强器), UserTokenVo user = new UserTokenVo(); // (省略.......) (自定义去数据库根据username查找数据库用户表对应的UmsUser对象) UmsUser umsUser = userService.findByUSername..........省略 if (umsUser == null) return null; // 把umsUser的属性值复制给UserTokenVo 省略....... // 返回 UserTokenVo return user; } }
配置认证中心的spring security
作用:
@EnableWebSecurity @Configuration public class OAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private Ouath2UserDetailService ouath2UserDetailService; // 重新注入认证管理器 @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } // 注入密码加密 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(ouath2UserDetailService) // 使用自定义方式加载用户信息 .passwordEncoder(passwordEncoder()); } }
/** * 增强颁发的token的携带信息 * * * */ public class CustomTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { // 这个UserTokenVo就是之前UserDetial返回的对象 //从那获取要增强携带的字段 UserTokenVo user = (UserTokenVo) authentication.getPrincipal(); final Map<String, Object> additionalInfo = new HashMap<>(); //添加token携带的字段 additionalInfo.put("id", user.getId()); additionalInfo.put("nickname", user.getNickname()); additionalInfo.put("avatar", user.getAvatar()); additionalInfo.put("description", user.getDescription()); additionalInfo.put("blog_address", user.getBlog_address()); DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken; token.setAdditionalInformation(additionalInfo); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); return accessToken; } }
作用:
配置这个类后已经帮我们实现了oauth2协议的大部分操作和持久化配置(之前创建的表),什么颁发token,校验token,刷新token,这些个功能都暴露成固定的请求接口供外部调用
/** ouath2 认证服务器配置 * * * */ @Configuration @EnableAuthorizationServer // 开启认证服务器 public class Ouath2AuthServerConfig extends AuthorizationServerConfigurerAdapter { private final AuthenticationManager authenticationManager; private final DataSource dataSource; private Ouath2UserDetailService ouath2UserDetailService; public Ouath2AuthServerConfig(AuthenticationManager authenticationManager, DataSource dataSource, Ouath2UserDetailService ouath2UserDetailService ) { this.authenticationManager = authenticationManager; this.dataSource = dataSource; this.ouath2UserDetailService = ouath2UserDetailService; } /** =================================配置============================================================= * 1. */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.jdbc(dataSource); //这里就是配置之前sql脚本的数据源 } /** * 2.认证服务器安全配置 */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { //基于session认证会用到 /* security//.checkTokenAccess("isAuthenticated()") // 认证中心往外面暴露的一个用来获取jwt的SigningKey的服务/oauth/token_key,但我选择在每个资源服务器本地配置SigningKey //.tokenKeyAccess("isAuthenticated()") */ } /** * 3. 配置customTokenEnhancer,自定义UserDetailService,token存储策略 */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { // 将增强的token设置到增强链中 TokenEnhancerChain enhancerChain = new TokenEnhancerChain(); enhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer(), jwtAccessTokenConverter())); endpoints.tokenStore(tokenStore()) .authenticationManager(authenticationManager) // //刷新token的请求会用用到 .userDetailsService(ouath2UserDetailService) .tokenEnhancer(enhancerChain); } } //========================注入===================================== // 更改存储token的策略,默认是内存策略,修改为jwt @Bean public TokenStore tokenStore() { //return new JdbcTokenStore(dataSource); //基于session认证 return new JwtTokenStore(jwtAccessTokenConverter()); //基于token认证 } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter(){ JwtAccessTokenConverter jat = new JwtAccessTokenConverter(); jat.setSigningKey("909090"); // jwt使用这个key来签名,验证token的服务也使用这个key来验签 return jat; } // 添加自定义token增强器实现颁发额外信息的token,因为默认颁发token的字段只有username和ROLE @Bean public TokenEnhancer customTokenEnhancer() { return new CustomTokenEnhancer(); // 自定义实现 }
打开postman申请token:
1.点击authorization,使用basic-autu,设置申请令牌的客户端应用信息
如果用户账号正确,basic-ahth页设置正确将会返回如下信息:
复制access_token去jwt官网解析
地址:
https://jwt.io/
目的:
<!-- security ouath2 -->
<! -- 核心依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!--导入配置文件处理器,配置文件进行绑定就会有提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
作用:
public class CustomUserAuthenticationConverter implements UserAuthenticationConverter { private static final String N_A = "N/A"; @Override public Authentication extractAuthentication(Map<String, ?> map) { // 这个map就是存放token各个字段对应的信息 if (!map.containsKey(USERNAME)) return null; // CurrentUserInfo自定义当前登陆用户对象,存放token解析出来的信息(省略......) CurrentUserInfo user = new CurrentUserInfo(); user.setId((String) map.get("id")); user.setUsername((String) map.get(USERNAME)); user.setNickname((String) map.get("nickname")); user.setAvatar((String) map.get("avatar")); // 有权限信息就格式化权限信息 if (map.containsKey("authorities") && map.get("authorities") != null){ Collection<? extends GrantedAuthority> authorities = getAuthorities(map); user.setAuthorities(authorities); return new UsernamePasswordAuthenticationToken(user, N_A,authorities); }else { return new UsernamePasswordAuthenticationToken(user, N_A,null); } } private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) { Object authorities = map.get(AUTHORITIES); if (authorities instanceof String) { return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities); } if (authorities instanceof Collection) { return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils .collectionToCommaDelimitedString((Collection<?>) authorities)); }else if (authorities == null){ } throw new IllegalArgumentException("Authorities must be either a String or a Collection"); } // 复制 DefaultUserAuthenticationConverter @Override public Map<String, ?> convertUserAuthentication(Authentication authentication) { Map<String, Object> response = new LinkedHashMap<String, Object>(); response.put(USERNAME, authentication.getName()); if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) { response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities())); } return response; } }
@Data public class CustomTokenServices implements ResourceServerTokenServices { private TokenStore tokenStore; //认证策略,基于内存,jdbc,jwt,redis..... private DefaultAccessTokenConverter defaultAccessTokenConverter; private JwtAccessTokenConverter jwtAccessTokenConverter; @Override public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException { //从token中解析得到authentication OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(accessToken); // Map<String, ?> map = jwtAccessTokenConverter.convertAccessToken(readAccessToken(accessToken), oAuth2Authentication); //根据重新设置默认转换器再去获取authentication (稍后在资源服务器配置类注入CustomUserAuthenticationConverter) return defaultAccessTokenConverter.extractAuthentication(map); } @Override public OAuth2AccessToken readAccessToken(String accessToken) { return tokenStore.readAccessToken(accessToken); } }
作用:
先编写一个添加auth-client依赖时,资源服务器的自动属性配置类,把功能属性配置暴露出去
@Data
@Component
@ConfigurationProperties(prefix = "custom.ouath2.client")
public class AuthClientProperties {
//与签名jwt时使用的密钥,否则解析token失败
private String signingKey;
//声明资源服务器名称
//在oauth_client_details表里的resource_ids字段就是那个客户端可访问的资源服务器列表.
private String resourceId;
// 资源服务器自定义放行的请求列表
private List<String> ignoreUrls;
}
配置资源服务器:
/** * 资源服务器配置 * * @author burukeyou * */ @Slf4j @Configuration @EnableResourceServer //注册为资源服务器 @ComponentScan("burukeyou.auth.authClient") public class CustomResourceServerConfig extends ResourceServerConfigurerAdapter { // spring security 里的AuthenticationEntryPoint,就是自定义认证失败时的处理方式,默认是返回它自己的错误信息 //实现AuthenticationEntryPoint接口即可(省略.........) @Autowired private CustomAuthenticationEntryPoint baseAuthenticationEntryPoint; // 自定义配置类 @Autowired private AuthClientProperties authClientProperties; // 注入密码加密 /*@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }*/ @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable(); // 放行 swagger ui (有整合swagger就放行这些请求吧) http.authorizeRequests().antMatchers("/v2/api-docs", "/swagger-resources/configuration/ui", "/swagger-resources","/swagger-resources/configuration/security", "/swagger-ui.html","/course/coursebase/**","/webjars/**","/api/**/v2/api-docs").permitAll(); // 根据自定义配置url放行 if (authClientProperties.getIgnoreUrls() != null){ for(String url: authClientProperties.getIgnoreUrls()){ http.authorizeRequests().antMatchers(url).permitAll(); } } // 其他请求均需要token才能访问 http.authorizeRequests().anyRequest().authenticated(); // http.authorizeRequests().anyRequest().permitAll(); } @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { if (authClientProperties.getResourceId() != null) resources.resourceId(authClientProperties.getResourceId()); // 这里的签名key 保持和认证中心一致 if (authClientProperties.getSigningKey() == null) log.info("SigningKey is null cant not decode token......."); // DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter(); accessTokenConverter.setUserTokenConverter(new CustomUserAuthenticationConverter()); // JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey(authClientProperties.getSigningKey()); //设置解析jwt的密钥 converter.setVerifier(new MacSigner(authClientProperties.getSigningKey())); // CustomTokenServices tokenServices = new CustomTokenServices(); // 在CustomTokenServices注入三个依赖对象 tokenServices.setTokenStore(new JwtTokenStore(converter)); //设置token存储策略 tokenServices.setJwtAccessTokenConverter(converter); tokenServices.setDefaultAccessTokenConverter(accessTokenConverter); resources.tokenServices(tokenServices) .authenticationEntryPoint(baseAuthenticationEntryPoint); } }
在编写一个工具类方便以后获取到当前登陆用户信息
public class AuthUtils {
private AuthUtils(){}
/**
* get current login user info
* @return
*/
public static CurrentUserInfo getCurrentUser(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication != null ? (CurrentUserInfo)authentication.getPrincipal():null;
}
}
大致流程:
<!-- auth - client-->
<dependency>
<groupId>com.burukeyou</groupId>
<artifactId>auth-client</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
3.编写application.yml自定义资源服务器配置
custom:
ouath2:
client:
# jwt的密钥
signingKey: 909090
resourceId: ${spring.application.name}
# 放行的url
ignoreUrls:
- /oauth/**
- /user/**
4.然后随便写个controller
比如:
@RestController @RequestMapping("/search") @Api(value = "全局搜索接口",description = "...") public class GlobalController { @GetMapping("/a") @PreAuthorize("isAuthenticated()") public String get(@AuthenticationPrincipal CurrentUserInfo userInfo){ return "1"; } @GetMapping("/b") @ApiOperation(value = "查找2",notes = "xx") public String get02(@AuthenticationPrincipal CurrentUserInfo userInfo){ Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return "2"; } @GetMapping("/c") public String get03(@AuthenticationPrincipal CurrentUserInfo userInfo){ Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return "3"; } }
随便调用这些接口可发现除/oauth/**和/user/**路径被放行,其他均需要校验token才能访问
再来比如访问 /search/c 接口
查看此时 /search/c 接口 debug信息
完
如果觉得文章有用,你可鼓励下作者
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。