核心配置类详解
认证中心配置类:
package com.fen.dou.config;
import com.fen.dou.component.JwtTokenEnhancer;
import com.fen.dou.properties.JwtCAProperties;
import com.fen.dou.service.UserDetailService;
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.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.TokenEnhancerChain;
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 javax.sql.DataSource;
import java.security.KeyPair;
import java.util.Arrays;
@Configuration
@EnableAuthorizationServer
@EnableConfigurationProperties(value = JwtCAProperties.class)
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Autowired
private UserDetailService userDetailService;
@Autowired
private JwtCAProperties jwtCAProperties;
/**
* 方法实现说明:我们颁发的token通过jwt存储
*/
@Bean
public TokenStore tokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//jwt的密钥
converter.setKeyPair(keyPair());
return converter;
}
@Bean
public KeyPair keyPair() {
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource(jwtCAProperties.getKeyPairName()), jwtCAProperties.getKeyPairSecret().toCharArray());
return keyStoreKeyFactory.getKeyPair(jwtCAProperties.getKeyPairAlias(), jwtCAProperties.getKeyPairStoreSecret().toCharArray());
}
@Bean
public JwtTokenEnhancer tokenEnhancer() {
return new JwtTokenEnhancer();
}
/**
* 方法实现说明:认证服务器能够给哪些 客户端颁发token 我们需要把客户端的配置 存储到
* 数据库中 可以基于内存存储和db存储
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetails());
}
/**
* 方法实现说明:用于查找我们第三方客户端的组件 主要用于查找 数据库表 oauth_client_details
*/
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
/**
* 方法实现说明:授权服务器的配置的配置
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(),jwtAccessTokenConverter()));
endpoints.tokenStore(tokenStore()) //授权服务器颁发的token 怎么存储的
.tokenEnhancer(tokenEnhancerChain)
.userDetailsService(userDetailService) //用户来获取token的时候需要 进行账号密码
.authenticationManager(authenticationManager);
}
/**
* 方法实现说明:授权服务器安全配置
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//第三方客户端校验token需要带入 clientId 和clientSecret来校验
security .checkTokenAccess("isAuthenticated()")
.tokenKeyAccess("isAuthenticated()");//来获取我们的tokenKey需要带入clientId,clientSecret
security.allowFormAuthenticationForClients();
}
}
@EnableAuthorizationServer这个注解就是生成一些rest接口供我们调用
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
public @interface EnableAuthorizationServer {
}
点击进入AuthorizationServerEndpointsConfiguration类
@Bean
public AuthorizationEndpoint authorizationEndpoint() throws Exception {
..................................
}
@Bean
public TokenEndpoint tokenEndpoint() throws Exception {
..................................
}
@Bean
public CheckTokenEndpoint checkTokenEndpoint() {
..................................
}
AuthorizationEndpoint 提供: /oauth/authorize 接口(包括GET/POST请求)
TokenEndpoint提供: /oauth/token 接口(现在只支持POST请求,GET请求会抛异常)
CheckTokenEndpoint 提供: /oauth/check_token(只支持GET请求)
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetails());
}
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
这是存储第三方客户端信息,用数据库进行存储,也支持内存存储,如果数据存储的话,需要添加几张表:
/**
* 方法实现说明:授权服务器的配置的配置
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(),jwtAccessTokenConverter()));
endpoints.tokenStore(tokenStore()) //授权服务器颁发的token 怎么存储的
.tokenEnhancer(tokenEnhancerChain)
.userDetailsService(userDetailService) //用户来获取token的时候需要 进行账号密码
.authenticationManager(authenticationManager);
}
这个配置的作用是服务器端对token的管理,对用户身份认证,包括token存储,token生成及增强
token存储:可以用session存储,数据库存储,redis存储,jwt存储
jwt存储
优点:1、在一个服务请求的时候,不用每次都去认证中心check_token,可以提升访问性能
2、不用在内存中存储token,节省资源
缺点:1、jwt字符串比较长,网络传输要稍微慢一点
session存储缺点是:占用本地内存,而且过期时间不好管理,需要依赖Cooke
redis存储缺点是:过期时间不好管理,需要依赖Cooke(可以用spring session实现),而且内存是宝贵资源,比较昂贵
数据库存储缺点是:性能很差,每一次check_token都要去数据库查一遍是否正确,是否过期
session存储,redis存储,数据库存储等优点:token是随机,字符串比较短
所以最好的选择就是JWT方式,当然也要看具体业务场景,如果服务器带宽不够,网络传输比较慢,也可以考虑用redis
token增强:
@Bean
public JwtTokenEnhancer tokenEnhancer() {
return new JwtTokenEnhancer();
}
这个是对jwt进行数据增强的,就是在jwt payload中添加自定义用户数据
如以下token:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXV0aC1zZXJ2ZXIiXSwidXNlcl9uYW1lIjoiYWRtaW4iLCJzY29wZSI6WyJyZWFkIl0sImFkZGl0aW9uYWxJbmZvIjp7Im5pY2tOYW1lIjoi566h55CG5ZGYIiwibWVtYmVySWQiOjF9LCJleHAiOjE2NDc2ODUzMzEsImF1dGhvcml0aWVzIjpbIi9vcmRlci9saXN0IiwiL29yZGVyL3NhdmVPcmRlciIsIi9vcmRlci9zZWxlY3RPcmRlckluZm9CeUlkQW5kVXNlck5hbWUiLCIvcHJvZHVjdC9zZWxlY3RQcm9kdWN0SW5mb0J5SWQiXSwianRpIjoiMDU0MzU0ZWEtODAwNS00ZmU4LTgxYjctNzhkZDU5NzFkMTIzIiwiY2xpZW50X2lkIjoicG9ydGFsX2FwcCJ9.CwxfT7Q6lIn-rddV97zVBnR2FBWA5MEq9WH2x9DX6lukKpGen9e9YNqmcrCJJpuAscylXSd_QN6-lvSH5IDqpQeJLMNuzUdMfz41eYIw7VABrn6IL0CMUY6i-4nD-lZSfJQbwQhr0c-RNMPxvcQb6FUHO2s8WIxElSt_8pN3sib47qAo8g4HZBCP-OCrB4as49qS1buiblwOFZxs8uGE9MiM83dsSNNPXeKEe7p0Whgovft4DJ2D-bmPwUyDbAMs0Lec-gPqbClO2aPZ5Rgv6mYOFASvQ_F8ZPSRSBCf6NbouB9SlPgWHMddTOcEo_rnLRFEN-_fwuqAz2OItPsJNQ
在JSON Web Tokens - jwt.io 网站中可以进行解析,JwtTokenEnhancer 作用就是添加下图中圈住的部分,
package com.fen.dou.service;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fen.dou.entity.*;
import com.fen.dou.mapper.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* @vlog: 高于生活,源于生活
* @desc: 类的描述:认证服务器加载用户的类
*/
@Slf4j
@Component
public class UserDetailService implements UserDetailsService {
/**
* 方法实现说明:用户登陆
*/
@Autowired
private SysUserMapper userMapper;
@Autowired
private SysUserRoleMapper sysUserRoleMapper;
@Autowired
private SysPermissionMapper sysPermissionMapper;
@Autowired
private SysRolePermissionMapper sysRolePermissionMapper;
//密码加密组件
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
List authorityList = new ArrayList<>();
if(StringUtils.isEmpty(userName)) {
log.warn("用户登陆用户名为空:{}",userName);
throw new UsernameNotFoundException("用户名不能为空");
}
SysUser sysUser = getByUsername(userName);
if(null == sysUser) {
log.warn("根据用户名:{}查询用户信息为空",userName);
throw new UsernameNotFoundException(userName);
}
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper();
queryWrapper.eq(SysUserRole::getUserId,sysUser.getId());
List userRoleList = sysUserRoleMapper.selectList(queryWrapper);
userRoleList.stream().forEach(x->{
LambdaQueryWrapper sysRolePermissionLambdaQueryWrapper = new LambdaQueryWrapper();
sysRolePermissionLambdaQueryWrapper.eq(SysRolePermission::getRoleId,x.getRoleId());
List sysRolePermissionList = sysRolePermissionMapper.selectList(sysRolePermissionLambdaQueryWrapper);
List permisionIds = sysRolePermissionList.stream().map(y->y.getPermissionId()).collect(Collectors.toList());
List permissionList = sysPermissionMapper.selectBatchIds(permisionIds);
permissionList.forEach(z->{
authorityList.add(new SimpleGrantedAuthority(z.getUri()));
});
});
AuthUser authUser = new AuthUser(sysUser.getUsername(),passwordEncoder.encode(sysUser.getPassword()),authorityList);
authUser.setUserId(sysUser.getId());
authUser.setNickName(sysUser.getNickname());
authUser.setEmail(sysUser.getEmail());
log.info("用户登陆成功:{}", JSON.toJSONString(authUser));
return authUser;
}
/**
* 方法实现说明:根据用户名获取用户信息
*/
public SysUser getByUsername(String username) {
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper();
queryWrapper.eq(SysUser::getUsername,username);
List memberList = userMapper.selectList(queryWrapper);
if (!CollectionUtils.isEmpty(memberList)) {
return memberList.get(0);
}
return null;
}
}
这个类主要是对用户信息认证,并且查询出用户的权限信息,权限信息会注入JWT中
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailService userDetailService;
@Autowired
private AuthLogoutSuccessHandler logoutSuccessHandler;
/**
* 方法实现说明:用于构建用户认证组件,需要传递userDetailsService和密码加密器
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
}
/**
* 设置前台静态资源不拦截
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/assets/**", "/css/**", "/images/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login")
.and()
.logout()
.logoutSuccessHandler(logoutSuccessHandler)
.and()
.authorizeRequests()
.antMatchers("/login","/publickey/jwks.json","/login.html","/user/getCurrentUser").permitAll()
.anyRequest()
.authenticated()
.and().csrf().disable().cors();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
public static void main(String[] args) {
System.out.println(new BCryptPasswordEncoder().encode("admin"));
}
}
这个配置类主要是配置对密码加密的加密器,对认证中登录页面配置及对一些服务请求和静态文件请求不进行拦截的配置
注意:认证中心可以支持第三方系统所有的授权模式(包括:密码模式,授权码模式等),具体可以在oauth_client_details中查看
授权码模式必须在请求地址中添加 回调地址,并且和数据库对应的回调地址是一致的
密码模式生成token
Content-Type:application/x-www-form-urlencoded
返回信息为:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXV0aC1zZXJ2ZXIiXSwidXNlcl9uYW1lIjoiYWRtaW4iLCJzY29wZSI6WyJyZWFkIl0sImFkZGl0aW9uYWxJbmZvIjp7Im5pY2tOYW1lIjoi566h55CG5ZGYIiwibWVtYmVySWQiOjF9LCJleHAiOjE2NDc2OTg3MTAsImF1dGhvcml0aWVzIjpbIi9vcmRlci9saXN0IiwiL29yZGVyL3NhdmVPcmRlciIsIi9vcmRlci9zZWxlY3RPcmRlckluZm9CeUlkQW5kVXNlck5hbWUiLCIvcHJvZHVjdC9zZWxlY3RQcm9kdWN0SW5mb0J5SWQiXSwianRpIjoiYzIzYzlhYzctZGFhYS00ODcyLTlmZjQtMzQ5ZDlhYmJmMDA5IiwiY2xpZW50X2lkIjoicG9ydGFsX2FwcCJ9.OSSvY1FS9sgYBdufse4TR26nhVNDgmtCYY4BSqx64xya82uQl9RKfFw7yILaa-Dg0qKNT_RzANRVrHS5brQxWtgH1xylwJXOlWRZwwlkUgjnz-5RlIwDBv0YzMzCn6ymkq3XCePY6TYYmP5GFOFTtjnYTXYA3WiX8Q3lGGA5MhC3PnzKHDObxcGr18AJCLrOJ4GukXu9WrJ9Z1i-pDHFjSq7WGZEwlpQELkbVLWSwpQCiUPrpAB8EawUJ8zM4ulpI5fz3XZ2Her4Yt9-tmiAG63KT_fTWKg-BdfO_o4u7nxhNe7mwu1PzvGvA0rfMj0M-Jtf_9MuatuwJTAC9QRoAA",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXV0aC1zZXJ2ZXIiXSwidXNlcl9uYW1lIjoiYWRtaW4iLCJzY29wZSI6WyJyZWFkIl0sImF0aSI6ImMyM2M5YWM3LWRhYWEtNDg3Mi05ZmY0LTM0OWQ5YWJiZjAwOSIsImFkZGl0aW9uYWxJbmZvIjp7Im5pY2tOYW1lIjoi566h55CG5ZGYIiwibWVtYmVySWQiOjF9LCJleHAiOjE2NDc3MDA1MDksImF1dGhvcml0aWVzIjpbIi9vcmRlci9saXN0IiwiL29yZGVyL3NhdmVPcmRlciIsIi9vcmRlci9zZWxlY3RPcmRlckluZm9CeUlkQW5kVXNlck5hbWUiLCIvcHJvZHVjdC9zZWxlY3RQcm9kdWN0SW5mb0J5SWQiXSwianRpIjoiZDdlMWQ1M2MtMjNlZS00NDE5LWI3MmQtMWMxYjEyM2JhYzVkIiwiY2xpZW50X2lkIjoicG9ydGFsX2FwcCJ9.RD2ODe4bPjelQTAcoruVtiwnSqCP3eGEqbbbzFqAnUwhUgs9asIAr5hcG0y0VKcWP5WjWmrINAb6sVZf5YnC4gY_Xc3mG8FgQSSnzWp9vvI8pMl6tTpuzDEjcRtZu8UXzbXAuM0hL1lh3yV77aUn1ZrIXe2WEnlEWxV8Wp9qzekIK-EkqHhZ7rare4W5bwfjR7zd02ajdqriQ54eXqPB2htAXet3HN0wTAmRs6PUJGguj3vsbUfWhIN780H-jgl02UjyunJXSDSAD0uwzEXBnWydeSPbxUICBxDwK69TdfQAFdL3WRbxs3jtudgdU3sVlZZkezyaS6F2uajubnlB9Q",
"expires_in": 1799,
"scope": "read",
"additionalInfo": {
"nickName": "管理员",
"memberId": 1
},
"jti": "c23c9ac7-daaa-4872-9ff4-349d9abbf009"
}
授权码模式生成token
1、访问地址:http://localhost:9999/oauth/authorize?redirect_uri=https://www.baidu.com&response_type=code&client_id=portal_app
注意以上三个参数必填