1.导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.10.RELEASE</version>
</dependency>
2.认证服务配置
package com.itheima.auth.config;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
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.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
/**
* 开启认证服务器配置类
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Autowired
private AuthenticationManager authenticationManager;
/**
* 注册bcrypt加密对象
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 定义用户详情服务:框架会调用该对象 UserDetailsService(封装用户名密码,权限) 查询用户信息用于判断用户认证信息合法。
* TODO:将来将用户存入MySQL数据库中
*
* @return
*/
@Bean
public UserDetailsService userDetailsService(){
//在内存中提供自定义的用户名 密码
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//在内存中自定义用户名称:jack 密码:jack 权限:p1
manager.createUser(User.withUsername("third").password(passwordEncoder().encode("third")).authorities("user:query").build());
manager.createUser(User.withUsername("jack").password(passwordEncoder().encode("jack")).authorities("p1").build());
manager.createUser(User.withUsername("rose").password(passwordEncoder().encode("rose")).authorities("p2").build());
return manager;
}
/**
* 配置客户端详情:移动端、web端,第三方应用 定义客户端ID,秘钥
* third_client:提供授权码模式测试客户端 适用其他的第三方系统
* app_client,pc_client 密码模式 适用受信任客户端
* @param clients
* - clientId:(必须的)用来标识客户的Id(理解为第三方应用账户)
* - secret:(需要值得信任的客户端)客户端安全码,如果有的话。
* - scope:用来限制客户端的访问范围,可选值(read,write,all)如果为空(默认)的话,那么客户端拥有全部的访问范围。
* - authorizedGrantTypes:此客户端可以使用的授权类型,默认为空。 可选值( **`authorization_code`** , **`password`,`implicit,client_credentials,refresh_token`** )
* - authorities:此客户端可以使用的权限(基于Spring Security authorities)。
* - autoApproveScopes: 设置是否自动授权
* - redirectUris 授权码模式中重定向地址
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//客户端信息暂存储在内存中,也可改为security提供的表存储
//授权码模式下需要的客户端信息如下
clients.inMemory()
.withClient("third_client") //客户端标识
.secret(passwordEncoder().encode("third_secret")) //客户端的秘钥
.authorizedGrantTypes("authorization_code")
.autoApprove(true)
.scopes("all")
//.autoApprove("all")
.redirectUris("http://www.baidu.com") //设置回调地址
//密码模式需要的客户端信息
.and()
.withClient("app_client") //客户端标识
.secret(passwordEncoder().encode("app_secret")) //客户端的秘钥
.authorizedGrantTypes("password", "refresh_token")
.scopes("all") //设置回调地址
.and()
.withClient("pc_client")
.secret(passwordEncoder().encode("pc_secret"))
.authorizedGrantTypes("password", "refresh_token")
.scopes("all");
}
/**
* 令牌服务支持
*
* @return
*/
@Bean
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service = new DefaultTokenServices();
//是否支持刷新令牌
service.setSupportRefreshToken(true);
//令牌存储
service.setTokenStore(tokenStore);
// 令牌有效期单位秒,默认默认12小时 设置为1周
service.setAccessTokenValiditySeconds(604800);
// 刷新令牌默认单位秒 默认30天 设置为3周
service.setRefreshTokenValiditySeconds(1814400);
return service;
}
/**
* 注入令牌策略
* @param endpoints
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager)
.tokenServices(tokenService());
}
/**
* **令牌端点的安全约束,对密码模式表单提交允许**
* @param security
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.allowFormAuthenticationForClients(); //支持密码模式下表单登录
}
}
3.token加密方式配置
package com.happyu.auth.config;
import jdk.nashorn.internal.parser.Token;
import org.junit.Before;
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.InMemoryTokenStore;
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 java.security.KeyPair;
@Configuration
public class TokenConfig {
//对称加密采用秘钥
private static final String secret = "itcast_auth";
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory(new ClassPathResource("oauth2.jks"), "itcast".toCharArray()).getKeyPair("oauth2");
jwtAccessTokenConverter.setKeyPair(keyPair);
// jwtAccessTokenConverter.setSigningKey(secret);
return jwtAccessTokenConverter;
}
/**
* 默认:InMemoryTokenStore,内存中生成一个普通的令牌uuid。
* @return
*/
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
}
4.密码模式下需要:用户认证时需要的认证管理和用户信息来源
package com.happyu.auth.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 密码模式下需要:用户认证时需要的认证管理和用户信息来源
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
5.配置认证器
package com.happyu.auth.integration.authenticator;
import com.happyu.sys.dto.CompanyUserDTO;
import javax.servlet.http.HttpServletRequest;
/**
* @author heima
* 登录认证器接口
*/
public interface LoginHandler {
/**
* 远程调用系统微服务得到用户信息
*
* @param request Http请求对象
* @return 用户表实体
*/
CompanyUserDTO queryUser(HttpServletRequest request);
/**
* 认证器各个实现类
* 判断当前客户端提交认证类型是否支持当前认证器
*
* @param request 请求对象
* @return true:采用当前认证器 false:不支持
*/
boolean support(HttpServletRequest request);
}
6.自定义UserDetailService
package com.happyu.auth.integration.service;
import cn.hutool.core.collection.CollectionUtil;
import com.happyu.auth.integration.authenticator.LoginHandler;
import com.happyu.common.threadlocals.UserHolder;
import com.happyu.common.util.BeanHelper;
import com.happyu.common.util.JsonUtils;
import com.happyu.common.vo.UserInfo;
import com.happyu.sys.dto.CompanyUserDTO;
import com.happyu.sys.entity.Function;
import com.happyu.sys.entity.Role;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义用户详情服务对象,框架查询该对象方法得到用户信息UserDetails
* @author: itheima
* @create: 2021-09-19 11:02
*/
@Slf4j
@Component
public class MyUserDetailsService implements UserDetailsService {
/**
* 当前认证器集合
*/
@Autowired
private List<LoginHandler> loginHandlers;
/**
* 框架认证过程调用该方法得到用户信息
* @param username 用户名
* @return 用户详情对象-包含用户名称,用户正确密码,用户权限
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("调用到自定义详情服务MyUserDetailsService--查询用户信息");
//1.获取用户提交认证方式
//1.1 如果在普通类中获取请求参数 采用RequestContextHolder请求上下文对象
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//1.2 转为ServletRequestAttributes
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
//1.3 获取请求对象 进一步 获取请求参数
HttpServletRequest request = servletRequestAttributes.getRequest();
log.info("认证参数:"+request.getParameter("username") +", "+request.getParameter("auth_type"));
//2.根据客户端认证方式,选择一个认证器进行 获取数据库中 员工信息
//选择认证器
LoginHandler loginHandler = chooseLoginHandler(request);
CompanyUserDTO companyUserDTO = loginHandler.queryUser(request);
//3.封装框架要求的返回结果 UserDetails 用户详情服务
//3.1 封装用户权限信息
List<GrantedAuthority> authorities = this.getAuthorities(companyUserDTO);
if (CollectionUtils.isEmpty(authorities)) {
//如果用户没有权限 增加 游客 角色
authorities.add(new SimpleGrantedAuthority("ROLE_USER_TOURIST"));
}
//3.2 User对象参数一:生成jwt令牌中 user_name 属性值 问题:将来资源服务器无法得到用户ID标识
//3.3 解决 将CompanyUserDTO转为 自定义对象:UserInfo
UserInfo userInfo = BeanHelper.copyProperties(companyUserDTO, UserInfo.class);
//将用户信息存入userholder
UserHolder.setUser(userInfo);
return new User(JsonUtils.toJsonStr(userInfo), companyUserDTO.getPassword(), authorities);
//x.后续框架还会继续进行校验用户信息,客户端信息是否合法 ,最终才会根据用户信息产生令牌
}
/**
* 根据用户角色跟权限 封装框架要求权限对象
* 将角色,权限字符串 封装权限对象
* 封装
* @param companyUserDTO
* @return
*/
private List<GrantedAuthority> getAuthorities(CompanyUserDTO companyUserDTO) {
//1.封装角色权限
List<GrantedAuthority> authorities = new ArrayList<>();
List<Role> roleList = companyUserDTO.getSysRoles();
if (CollectionUtil.isNotEmpty(roleList)) {
for (Role role : roleList) {
GrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(role.getRoleName());
authorities.add(simpleGrantedAuthority);
}
}
//2.封装权限
List<Function> functionList = companyUserDTO.getSysFunctions();
if (CollectionUtil.isNotEmpty(functionList)) {
for (Function function : functionList) {
GrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(function.getName());
authorities.add(simpleGrantedAuthority);
}
}
return authorities;
}
/**
* 根据传入请求参数判断 选择一个认证器实例返回
* @param request
* @return
*/
private LoginHandler chooseLoginHandler(HttpServletRequest request) {
//1.遍历认证器集合
for (LoginHandler loginHandler : loginHandlers) {
//2.每个认证器都有support方法 只要support返回true 直接返回当前实例对象
boolean support = loginHandler.support(request);
//2.1 进入某个方法实现
if (support) {
return loginHandler;
}
}
throw new OAuth2Exception("不支持当前认证方式");
}
}
6.在需要使用认证的微服务上添加资源配置和token配置