前言
捣鼓了一段时间的Spring cloud,想把公司的的基于用户和权限体系的基础框架也融入到Spring cloud中。查阅了一些相关资料,打算采用Spring Cloud Gateway+Spring Security OAuth2+JWT的方式。基础版本的已经搭好了,也总结一下这几天的成果。
授权服务 OAuth2+JWT
首先说明一下Spring Security OAuth2的交互数据可以有3种方式。
- 第一钟是基于配置的即内存
- 第二钟是基于内置数据结构的数据库(Spring Security OAuth2 自己定义了很多表结构和封装好的DAO 我们只要按照它给的表结构创建表就可以用了)
- 第三种就是我们自己定义的表
我有用了2和3,很多资料都是第一种,自己也研究了半天。不多说直接上代码
pom
如果采用redis存储token的话 要注意一下
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<!-- 指明版本,解决redis存储出现的问题:java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V问题 -->
<version>2.3.3.RELEASE</version>
</dependency>
授权服务器配置
这里主要是配置了用户的相关信息,由于我有自己的用户表所以我就采用了实现UserService接口的一个实现类。
package com.ljl.auth.config;
import com.ljl.auth.service.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
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.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.sql.DataSource;
/**
* Created by Administrator on 2019/9/3 0003.
* 安全配置
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserServiceImpl userService;
@Autowired
private DataSource dataSource;
/**
* 注入用户信息服务
* @return 用户信息服务对象
*/
@Bean
public UserDetailsService userDetailsService() {
return userService;
}
/**
* 全局用户信息
* @param auth 认证管理
* @throws Exception 用户认证异常信息
*/
@Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
//auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(passwordEncoder());
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
}
/**
* 认证管理
* @return 认证管理对象
* @throws Exception 认证异常信息
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* http安全配置
* @param http http安全对象
* @throws Exception http安全异常信息
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated().and()
.httpBasic().and().csrf().disable();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
授权服务器配置。这边主要是token和配置客户端详情信息。
由于我用了jwt所以就不需要将token存储下来了,所以只用了OAUTH_CLIENT_DETAILS这个表。
OAuth2集成了很多表,可以去查询相关资料。
jwt我采用的是RSA加密的方法,工具类我也贴出来了。
package com.ljl.auth.config;
import com.ljl.common.util.RSAUtil;
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.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.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.redis.RedisTokenStore;
import javax.sql.DataSource;
/**
* Created by Administrator on 2019/9/3 0003.
* 授权服务器配置
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
// 认证管理器
@Autowired
private AuthenticationManager authenticationManager;
// redis连接工厂
/*@Autowired
private JedisConnectionFactory JedisConnectionFactory;*/
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private DataSource dataSource;
/**
* 令牌存储
* @return redis令牌存储对象
*/
/*@Bean
public TokenStore tokenStore() {
return new RedisTokenStore(JedisConnectionFactory);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(this.authenticationManager);
endpoints.tokenStore(tokenStore());
}*/
@Bean
protected JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new MyJwtAccessTokenConverter();
converter.setKeyPair(RSAUtil.GetKeyPair());
return converter;
}
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(jwtTokenStore()).accessTokenConverter(jwtAccessTokenConverter()).authenticationManager(this.authenticationManager);
}
/**
* OAuth 授权端点开放
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
// 开启/oauth/token_key验证端口无权限访问
.tokenKeyAccess("permitAll()")
// 开启/oauth/check_token验证端口认证权限访问
.checkTokenAccess("isAuthenticated()")
//主要是让/oauth/token支持client_id以及client_secret作登录认证
.allowFormAuthenticationForClients();
}
/**
* OAuth 配置客户端详情信息
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource).passwordEncoder(passwordEncoder);
}
}
我用的是oracle
-- Create table
create table OAUTH_CLIENT_DETAILS
(
client_id VARCHAR2(255) not null,
resource_ids VARCHAR2(255),
client_secret VARCHAR2(255),
scope VARCHAR2(255),
authorized_grant_types VARCHAR2(255),
web_server_redirect_uri VARCHAR2(255),
authorities VARCHAR2(255),
access_token_validity NUMBER(11),
refresh_token_validity NUMBER(11),
additional_information VARCHAR2(255),
autoapprove VARCHAR2(255)
)
tablespace BASE
pctfree 10
initrans 1
maxtrans 255
storage
(
initial 64K
next 1M
minextents 1
maxextents unlimited
);
自定义用户
自定义用户表的话主要是需要实现UserService这个接口中的loadUserByUsername方法并返回一个uesr对象。
这边的查询用户是我另一个基础服务的一个接口,就是通过用户名查询数据。
package com.ljl.auth.service.impl;
import com.alibaba.fastjson.JSON;
import com.ljl.auth.model.*;
import com.ljl.auth.service.UserService;
import com.ljl.base.api.UserFeignApi;
import com.ljl.common.base.Pages;
import com.ljl.common.base.Result;
import com.ljl.common.model.BaseOperator;
import com.ljl.common.util.PasswordUtil;
import com.ljl.common.util.StaticData;
import com.ljl.dataSource.BaseDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.*;
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserFeignApi userFeignApi;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询用户信息
BaseOperator userinfo = userFeignApi.getUserByUsername(username);
//需要构造org.springframework.security.core.userdetails.User 对象包含账号密码还有用户的角色
if (userinfo!=null){
User user = new User(userinfo.getLoginname(),userinfo.getPassword(), AuthorityUtils.createAuthorityList("admin"));
return new OauthUser(userinfo,user);
}else {
throw new UsernameNotFoundException("用户["+username+"]不存在");
}
}
}
这个是我自己定义的实现了UserDetails, CredentialsContainer这两个接口的用户对象,除了再上面要用,jwt中也要用。
这边的baseUser 是我自己的用户对象。
package com.ljl.auth.model;
import com.ljl.common.model.BaseOperator;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
/**
* Created by Administrator on 2019/9/6 0006.
*/
public class OauthUser implements UserDetails, CredentialsContainer {
private final BaseOperator baseUser;
private final User user;
public OauthUser(BaseOperator baseUser, User user) {
this.baseUser = baseUser;
this.user = user;
}
@Override
public void eraseCredentials() {
user.eraseCredentials();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getAuthorities();
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return user.isAccountNonExpired();
}
@Override
public boolean isAccountNonLocked() {
return user.isAccountNonLocked();
}
@Override
public boolean isCredentialsNonExpired() {
return user.isCredentialsNonExpired();
}
@Override
public boolean isEnabled() {
return user.isEnabled();
}
public BaseOperator getBaseOperator() {
return baseUser;
}
}
jwt
jwt生成token 我们可以自己定义jwt里面的内容
package com.ljl.auth.config;
import com.ljl.auth.model.OauthUser;
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.store.JwtAccessTokenConverter;
import java.util.HashMap;
/**
* Created by Administrator on 2019/9/6 0006.
* 自定义的token
*/
public class MyJwtAccessTokenConverter extends JwtAccessTokenConverter {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
if (accessToken instanceof DefaultOAuth2AccessToken) {
//((DefaultOAuth2AccessToken) accessToken).setRefreshToken();
Object principal = authentication.getPrincipal();
if (principal instanceof OauthUser) {
OauthUser user = (OauthUser) principal;
HashMap<String, Object> map = new HashMap<>();
map.put("user_id", user.getBaseOperator().getUserid());
map.put("phone", user.getBaseOperator().getTelphonenum());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(map);
}
}
return super.enhance(accessToken, authentication);
}
}
RSA工具类
package com.ljl.common.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.Base64Utils;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Properties;
/**
* 实现RSA加解密
* @author : lijialun
* @description:
*/
public class RSAUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(RSAUtil.class);
/** 算法名称 */
private static final String ALGORITHM = "RSA";
/** RSA签名算法 */
private static final String RSA_SIGNATURE_ALGORITHM = "SHA256WithRSA";
/** 默认密钥大小 */
private static final int KEY_SIZE = 2048;
/** 最大解密长度 */
private static final int MAX_DECRYPT_BLOCK = 256;
/** 用来指定保存密钥对的文件名和存储的名称 */
private static final String PUBLIC_KEY_NAME = "publicKey";
private static final String PRIVATE_KEY_NAME = "privateKey";
private static final String PUBLIC_FILENAME = "publicKey.properties";
private static final String PRIVATE_FILENAME = "privateKey.properties";
private static Properties pubProperties;
private static Properties PriProperties;
/** 密钥对生成器 */
private static KeyPairGenerator keyPairGenerator = null;
private static KeyFactory keyFactory = null;
/** 缓存的密钥对 */
private static KeyPair keyPair = null;
/** Base64 编码/解码器 JDK1.8 */
private static Base64.Decoder decoder = Base64.getDecoder();
private static Base64.Encoder encoder = Base64.getEncoder();
/** 初始化密钥工厂 */
static{
try {
keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
keyFactory = KeyFactory.getInstance(ALGORITHM);
//generateKeyPair();
getInstanceForPub();
getInstanceForPri();
} catch (NoSuchAlgorithmException e) {
LOGGER.error(e.getMessage(),e);
}
}
/*初始化公钥config*/
private static Properties getInstanceForPub(){
if (pubProperties == null) {
Resource res =new ClassPathResource(PUBLIC_FILENAME);
pubProperties = new Properties();
try {
pubProperties.load(res.getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
return pubProperties;
}
/*初始钥私钥config*/
private static Properties getInstanceForPri(){
if (PriProperties == null) {
Resource res =new ClassPathResource(PRIVATE_FILENAME);
PriProperties = new Properties();
try {
PriProperties.load(res.getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
return PriProperties;
}
/** 私有构造器 */
private RSAUtil(){}
/**
* 生成密钥对
* 将密钥分别用Base64编码保存到#publicKey.properties#和#privateKey.properties#文件中
* 保存的默认名称分别为publicKey和privateKey
*
*
*/
public static synchronized void generateKeyPair(){
try {
keyPairGenerator.initialize(KEY_SIZE,new SecureRandom());
keyPair = keyPairGenerator.generateKeyPair();
} catch (InvalidParameterException e){
LOGGER.error("KeyPairGenerator does not support a key length of " + KEY_SIZE + ".",e);
} catch (NullPointerException e){
LOGGER.error("RSAUtils#key_pair_gen is null,can not generate KeyPairGenerator instance.",e);
}
RSAPublicKey rsaPublicKey = (RSAPublicKey)keyPair.getPublic();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey)keyPair.getPrivate();
String publicKeyString = encoder.encodeToString(rsaPublicKey.getEncoded());
String privateKeyString = encoder.encodeToString(rsaPrivateKey.getEncoded());
System.out.println("公钥:"+publicKeyString);
System.out.println("私钥:"+privateKeyString);
storeKey(publicKeyString,PUBLIC_KEY_NAME,pubProperties,PUBLIC_FILENAME);
storeKey(privateKeyString,PRIVATE_KEY_NAME,PriProperties,PRIVATE_FILENAME);
}
/**
* 将指定的密钥字符串保存到文件中,如果找不到文件,就创建
* @param keyString 密钥的Base64编码字符串(值)
* @param keyName 保存在文件中的名称(键)
* @param properties 目标文件
*/
private static void storeKey(String keyString,String keyName,Properties properties,String fileName){
try {
Resource res =new ClassPathResource(fileName);
FileOutputStream oFile = new FileOutputStream(res.getFile(), false);
properties.setProperty(keyName,keyString);
properties.store(oFile, keyName);
oFile.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取密钥字符串
* @param keyName 需要获取的密钥名
* @param properties 密钥文件
* @return Base64编码的密钥字符串
*/
private static String getKeyString(String keyName,Properties properties){
return properties.getProperty(keyName);
}
/**
* 从文件获取RSA公钥
* @return RSA公钥
* @throws InvalidKeySpecException
*/
public static RSAPublicKey getPublicKey(){
try {
byte[] keyBytes = decoder.decode(getKeyString(PUBLIC_KEY_NAME,pubProperties));
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(keyBytes);
RSAPublicKey rsa = (RSAPublicKey)keyFactory.generatePublic(x509EncodedKeySpec);
return rsa;
}catch (InvalidKeySpecException e) {
LOGGER.error("getPublicKey()#" + e.getMessage(),e);
}
return null;
}
/**
* 从文件获取RSA私钥
* @return RSA私钥
* @throws InvalidKeySpecException
*/
public static RSAPrivateKey getPrivateKey(){
try {
byte[] keyBytes = decoder.decode(getKeyString(PRIVATE_KEY_NAME,PriProperties));
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
return (RSAPrivateKey)keyFactory.generatePrivate(pkcs8EncodedKeySpec);
} catch (InvalidKeySpecException e) {
LOGGER.error("getPrivateKey()#" + e.getMessage(),e);
}
return null;
}
/**
* RSA公钥加密
*/
public static byte[] encryptByPublicKey(byte[] data) throws Exception {
// 对数据加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey());
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > 117) {
cache = cipher.doFinal(data, offSet, 117);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * 117;
}
byte[] encryptedData = out.toByteArray();
out.close();
return encryptedData;
}
//私钥解密
public static byte[] decryptByPrivateKey(byte[] encryptedData) throws Exception {
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(getPrivateKey().getEncoded());
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateK);
int inputLen = encryptedData.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
return decryptedData;
}
/**
* RSA公钥验签
*
* @param data
* 待签名字符串
* 公钥(Base64编码)
* @return 验签结果
* @throws Exception
*/
public static boolean verify(byte[] data, String sign) throws Exception {
Signature signature = Signature.getInstance(RSA_SIGNATURE_ALGORITHM);
signature.initVerify(getPublicKey());
signature.update(data);
return signature.verify(Base64Utils.decodeFromUrlSafeString(sign));
}
/**
* RSA私钥签名:签名方式SHA1withRSA
*
* @param data
* 待签名byte[]
* 私钥(Base64编码)
* @return 签名byte[]
* @throws Exception
*/
public static byte[] sign(byte[] data) throws Exception {
// Sign
Signature signature = Signature.getInstance(RSA_SIGNATURE_ALGORITHM);
signature.initSign(getPrivateKey());
signature.update(data);
return signature.sign();
}
/**
* 将char转换为byte
* @param c char
* @return byte
*/
private static byte toByte(char c){
return (byte)"0123456789ABCDEF".indexOf(c);
}
public static int getValidLength(byte[] bytes){
int i = 0;
if (null == bytes || 0 == bytes.length)
return i ;
for (; i < bytes.length; i++) {
if (bytes[i] == '\0')
break;
}
return i + 1;
}
public static KeyPair GetKeyPair(){
return new KeyPair(getPublicKey(),getPrivateKey());
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/15310.html