Commit b66930c6 by giaogiao

1.修改swagger分模块

2.修改权限相关
parent 82168ad8
......@@ -73,7 +73,17 @@ api-system api-app api-merchant
## 权限验证
使用shiro框架
Shiro 的核心:
Subject(主体): 用于记录当前的操作用户,Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授权,而subject是通过SecurityManager安全管理器进行认证授权
SecurityManager(安全管理器):对Subject 进行管理,他是shiro的核心SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。
Authenticator(认证器):对用户身份进行认证
Authorizer(授权器):用户通过认证后,来判断时候拥有该权限
realm:获取用户权限数据
sessionManager(会话管理):shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
CacheManager(缓存管理器):将用户权限数据存储在缓存,这样可以提高性能。
authc:所有url都必须认证通过才可以访问;
......
......@@ -39,7 +39,7 @@ public class AppHelloWorldController {
@GetMapping(value = "/needRole")
@OperationLog(name = "helloWorld")
@OperationLog(name = "needRole")
@ApiOperation(value = "Hello World", response = String.class)
public ApiResult<String> needRole() throws IOException {
log.debug("Hello World...app");
......@@ -47,7 +47,7 @@ public class AppHelloWorldController {
}
@GetMapping(value = "/noRole")
@OperationLog(name = "helloWorld")
@OperationLog(name = "noRole")
@ApiOperation(value = "Hello World", response = String.class)
public ApiResult<String> noRole() throws IOException {
log.debug("Hello World...app");
......
......@@ -38,7 +38,7 @@ import javax.servlet.http.HttpServletResponse;
@Slf4j
@RestController
//@Module("api-app")
@Api(value = "用户API", tags = {"APP相关"})
@Api(value = "用户API", tags = {"APP用户相关"})
@RequestMapping("/app/user/")
public class AppUserController extends BaseController {
......
......@@ -20,11 +20,15 @@ import com.alibaba.fastjson.JSON;
import io.geekidea.springbootplus.config.properties.JwtProperties;
import io.geekidea.springbootplus.config.properties.ShiroPermissionProperties;
import io.geekidea.springbootplus.config.properties.ShiroProperties;
import io.geekidea.springbootplus.framework.shiro.cache.LoginRedisService;
import io.geekidea.springbootplus.framework.shiro.cache.AppLoginRedisService;
import io.geekidea.springbootplus.framework.shiro.cache.MerchantLoginRedisService;
import io.geekidea.springbootplus.framework.shiro.cache.SysLoginRedisService;
import io.geekidea.springbootplus.framework.shiro.exception.ShiroConfigException;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtCredentialsMatcher;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtFilter;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtRealm;
import io.geekidea.springbootplus.framework.shiro.jwt.realm.JwtRealmAppUser;
import io.geekidea.springbootplus.framework.shiro.jwt.realm.JwtRealmMerchant;
import io.geekidea.springbootplus.framework.shiro.jwt.realm.JwtRealmSystem;
import io.geekidea.springbootplus.framework.shiro.service.ShiroLoginService;
import io.geekidea.springbootplus.framework.util.IniUtil;
import lombok.extern.slf4j.Slf4j;
......@@ -41,6 +45,7 @@ import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SessionStorageEvaluator;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
......@@ -103,13 +108,41 @@ public class ShiroConfig {
}
/**
* JWT数据源验证
* 系统的用户及权限数据
*
* @return
*/
@Bean
public JwtRealm jwtRealm(LoginRedisService loginRedisService) {
JwtRealm jwtRealm = new JwtRealm(loginRedisService);
public JwtRealmSystem jwtRealmSystem(SysLoginRedisService loginRedisService) {
JwtRealmSystem jwtRealm = new JwtRealmSystem(loginRedisService);
jwtRealm.setCachingEnabled(false);
jwtRealm.setCredentialsMatcher(credentialsMatcher());
return jwtRealm;
}
/**
* app的用户及权限数据
*
* @return
*/
@Bean
public JwtRealmAppUser jwtRealmAppUser(AppLoginRedisService appLoginRedisService) {
JwtRealmAppUser jwtRealm = new JwtRealmAppUser(appLoginRedisService);
jwtRealm.setCachingEnabled(false);
jwtRealm.setCredentialsMatcher(credentialsMatcher());
return jwtRealm;
}
/**
* 商家的用户及权限数据
*
* @return
*/
@Bean
public JwtRealmMerchant jwtRealmSystem(MerchantLoginRedisService merchantLoginRedisService) {
JwtRealmMerchant jwtRealm = new JwtRealmMerchant(merchantLoginRedisService);
jwtRealm.setCachingEnabled(false);
jwtRealm.setCredentialsMatcher(credentialsMatcher());
return jwtRealm;
......@@ -146,10 +179,10 @@ public class ShiroConfig {
* @return
*/
@Bean
public SecurityManager securityManager(LoginRedisService loginRedisService) {
public SecurityManager securityManager(SysLoginRedisService loginRedisService) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 数据连接器
securityManager.setRealm(jwtRealm(loginRedisService));
securityManager.setRealm(jwtRealmSystem(loginRedisService));
// 设置session存储方式: 不使用sessionId保存
securityManager.setSubjectDAO(subjectDAO());
SecurityUtils.setSecurityManager(securityManager);
......@@ -168,7 +201,7 @@ public class ShiroConfig {
@Bean(SHIRO_FILTER_NAME)
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,
ShiroLoginService shiroLoginService,
LoginRedisService loginRedisService,
SysLoginRedisService loginRedisService,
ShiroProperties shiroProperties,
JwtProperties jwtProperties) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
......@@ -191,7 +224,7 @@ public class ShiroConfig {
* @return
*/
private Map<String, Filter> getFilterMap(ShiroLoginService shiroLoginService,
LoginRedisService loginRedisService,
SysLoginRedisService loginRedisService,
JwtProperties jwtProperties) {
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put(JWT_FILTER_NAME, new JwtFilter(shiroLoginService, loginRedisService, jwtProperties));
......@@ -314,13 +347,15 @@ public class ShiroConfig {
/**
* 认证模式
*
* @param loginRedisService
* @param sysLoginRedisService
* @return
*/
@Bean
public Authenticator authenticator(LoginRedisService loginRedisService) {
public Authenticator authenticator(SysLoginRedisService sysLoginRedisService) {
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
authenticator.setRealms(Arrays.asList(jwtRealm(loginRedisService)));
List<Realm> realms = new ArrayList<>();
realms.add(jwtRealmSystem(sysLoginRedisService));
authenticator.setRealms(realms);
// 认证策略: 第一个成功
authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
return authenticator;
......
......@@ -72,6 +72,7 @@ import static springfox.documentation.swagger.schema.ApiModelProperties.findApiM
/**
* Swagger2全局配置
* 分组教程 https://sns.bladex.vip/q-342.html
*
* @author geekidea
* @date 2020/3/21
......@@ -128,12 +129,60 @@ public class Swagger2Config {
return Optional.fromNullable(input.declaringClass());
}
// @Bean
// public Docket createRestApi() {
// // 获取需要扫描的包
// String[] basePackages = getBasePackages();
// ApiSelectorBuilder apiSelectorBuilder = new Docket(DocumentationType.SWAGGER_2)
// .apiInfo(apiInfo("默认", "1.0"))
// .select();
// // 如果扫描的包为空,则默认扫描类上有@Api注解的类
// if (ArrayUtils.isEmpty(basePackages)) {
// apiSelectorBuilder.apis(RequestHandlerSelectors.withClassAnnotation(Api.class));
// } else {
// // 扫描指定的包
// apiSelectorBuilder.apis(basePackage(basePackages));
// }
// Docket docket = apiSelectorBuilder.paths(PathSelectors.any())
// .build()
// .enable(swaggerProperties.isEnable())
// .ignoredParameterTypes(ignoredParameterTypes)
// .globalOperationParameters(getParameters());
// return docket;
// }
@Bean
public Docket restAppApi() {
// 获取需要扫描的包
String[] basePackages = {"com.jumeirah.api.app.controller"};
ApiSelectorBuilder apiSelectorBuilder = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo("默认", "1.0"))
.groupName("app")
.select();
// 如果扫描的包为空,则默认扫描类上有@Api注解的类
if (ArrayUtils.isEmpty(basePackages)) {
apiSelectorBuilder.apis(RequestHandlerSelectors.withClassAnnotation(Api.class));
} else {
// 扫描指定的包
apiSelectorBuilder.apis(basePackage(basePackages));
}
Docket docket = apiSelectorBuilder.paths(PathSelectors.any())
.build()
.enable(swaggerProperties.isEnable())
.ignoredParameterTypes(ignoredParameterTypes)
.globalOperationParameters(getParameters());
return docket;
}
@Bean
public Docket createRestApi() {
public Docket restSysApi() {
// 获取需要扫描的包
String[] basePackages = getBasePackages();
String[] basePackages = {"com.jumeirah.api.system.controlle"};
ApiSelectorBuilder apiSelectorBuilder = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.apiInfo(apiInfo("默认", "1.0"))
.groupName("system")
.select();
// 如果扫描的包为空,则默认扫描类上有@Api注解的类
if (ArrayUtils.isEmpty(basePackages)) {
......@@ -150,12 +199,36 @@ public class Swagger2Config {
return docket;
}
@Bean
public Docket restMerchantApi() {
// 获取需要扫描的包
String[] basePackages = {"com.jumeirah.api.merchant.controller"};
ApiSelectorBuilder apiSelectorBuilder = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo("默认", "1.0"))
.groupName("merchant")
.select();
// 如果扫描的包为空,则默认扫描类上有@Api注解的类
if (ArrayUtils.isEmpty(basePackages)) {
apiSelectorBuilder.apis(RequestHandlerSelectors.withClassAnnotation(Api.class));
} else {
// 扫描指定的包
apiSelectorBuilder.apis(basePackage(basePackages));
}
Docket docket = apiSelectorBuilder.paths(PathSelectors.any())
.build()
.enable(swaggerProperties.isEnable())
.ignoredParameterTypes(ignoredParameterTypes)
.globalOperationParameters(getParameters());
return docket;
}
/**
* 获取apiInfo
*
* @return
*/
private ApiInfo apiInfo() {
private ApiInfo apiInfo(String title, String version) {
return new ApiInfoBuilder()
.title(swaggerProperties.getTitle())
.description(swaggerProperties.getDescription())
......
......@@ -30,7 +30,7 @@ import io.geekidea.springbootplus.config.properties.JwtProperties;
import io.geekidea.springbootplus.config.properties.SpringBootPlusProperties;
import io.geekidea.springbootplus.framework.common.api.ApiCode;
import io.geekidea.springbootplus.framework.common.api.ApiResult;
import io.geekidea.springbootplus.framework.shiro.cache.LoginRedisService;
import io.geekidea.springbootplus.framework.shiro.cache.SysLoginRedisService;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtToken;
import io.geekidea.springbootplus.framework.shiro.util.JwtTokenUtil;
import io.geekidea.springbootplus.framework.shiro.util.JwtUtil;
......@@ -78,7 +78,7 @@ public class SysLoginServiceImpl implements SysLoginService {
@Lazy
@Autowired
private LoginRedisService loginRedisService;
private SysLoginRedisService loginRedisService;
@Lazy
@Autowired
......
......@@ -28,7 +28,7 @@ import io.geekidea.springbootplus.framework.shiro.vo.LoginSysUserVo;
* @date 2019-09-30
* @since 1.3.0.RELEASE
**/
public interface LoginRedisService {
public interface AppLoginRedisService {
/**
* 缓存登录信息
......
/*
* Copyright 2019-2029 geekidea(https://github.com/geekidea)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.geekidea.springbootplus.framework.shiro.cache;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtToken;
import io.geekidea.springbootplus.framework.shiro.vo.LoginSysUserRedisVo;
import io.geekidea.springbootplus.framework.shiro.vo.LoginSysUserVo;
/**
* 登录信息Redis缓存操作服务
*
* @author geekidea
* @date 2019-09-30
* @since 1.3.0.RELEASE
**/
public interface MerchantLoginRedisService {
/**
* 缓存登录信息
*
* @param jwtToken
* @param loginSysUserVo
*/
void cacheLoginInfo(JwtToken jwtToken, LoginSysUserVo loginSysUserVo);
/**
* 刷新登录信息
*
* @param oldToken
* @param username
* @param newJwtToken
*/
void refreshLoginInfo(String oldToken, String username, JwtToken newJwtToken);
/**
* 通过用户名,从缓存中获取登录用户LoginSysUserRedisVo
*
* @param username
* @return
*/
LoginSysUserRedisVo getLoginSysUserRedisVo(String username);
/**
* 获取登录用户对象
*
* @param username
* @return
*/
LoginSysUserVo getLoginSysUserVo(String username);
/**
* 通过用户名称获取盐值
*
* @param username
* @return
*/
String getSalt(String username);
/**
* 删除对应用户的Redis缓存
*
* @param token
* @param username
*/
void deleteLoginInfo(String token, String username);
/**
* 判断token在redis中是否存在
*
* @param token
* @return
*/
boolean exists(String token);
/**
* 删除用户所有登录缓存
*
* @param username
*/
void deleteUserAllCache(String username);
}
/*
* Copyright 2019-2029 geekidea(https://github.com/geekidea)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.geekidea.springbootplus.framework.shiro.cache;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtToken;
import io.geekidea.springbootplus.framework.shiro.vo.LoginSysUserRedisVo;
import io.geekidea.springbootplus.framework.shiro.vo.LoginSysUserVo;
/**
* 登录信息Redis缓存操作服务
*
* @author geekidea
* @date 2019-09-30
* @since 1.3.0.RELEASE
**/
public interface SysLoginRedisService {
/**
* 缓存登录信息
*
* @param jwtToken
* @param loginSysUserVo
*/
void cacheLoginInfo(JwtToken jwtToken, LoginSysUserVo loginSysUserVo);
/**
* 刷新登录信息
*
* @param oldToken
* @param username
* @param newJwtToken
*/
void refreshLoginInfo(String oldToken, String username, JwtToken newJwtToken);
/**
* 通过用户名,从缓存中获取登录用户LoginSysUserRedisVo
*
* @param username
* @return
*/
LoginSysUserRedisVo getLoginSysUserRedisVo(String username);
/**
* 获取登录用户对象
*
* @param username
* @return
*/
LoginSysUserVo getLoginSysUserVo(String username);
/**
* 通过用户名称获取盐值
*
* @param username
* @return
*/
String getSalt(String username);
/**
* 删除对应用户的Redis缓存
*
* @param token
* @param username
*/
void deleteLoginInfo(String token, String username);
/**
* 判断token在redis中是否存在
*
* @param token
* @return
*/
boolean exists(String token);
/**
* 删除用户所有登录缓存
*
* @param username
*/
void deleteUserAllCache(String username);
}
package io.geekidea.springbootplus.framework.shiro.cache.impl;
import io.geekidea.springbootplus.config.constant.CommonRedisKey;
import io.geekidea.springbootplus.config.properties.JwtProperties;
import io.geekidea.springbootplus.framework.common.bean.ClientInfo;
import io.geekidea.springbootplus.framework.shiro.cache.AppLoginRedisService;
import io.geekidea.springbootplus.framework.shiro.convert.LoginSysUserVoConvert;
import io.geekidea.springbootplus.framework.shiro.convert.ShiroMapstructConvert;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtToken;
import io.geekidea.springbootplus.framework.shiro.vo.JwtTokenRedisVo;
import io.geekidea.springbootplus.framework.shiro.vo.LoginSysUserRedisVo;
import io.geekidea.springbootplus.framework.shiro.vo.LoginSysUserVo;
import io.geekidea.springbootplus.framework.util.ClientInfoUtil;
import io.geekidea.springbootplus.framework.util.HttpServletRequestUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.List;
import java.util.Set;
@Service
public class AppLoginRedisServiceImpl implements AppLoginRedisService {
@Autowired
private JwtProperties jwtProperties;
@Autowired
private RedisTemplate redisTemplate;
/**
* key-value: 有过期时间-->token过期时间
* 1. tokenMd5:jwtTokenRedisVo
* 2. username:loginSysUserRedisVo
* 3. username:salt
* hash: 没有过期时间,统计在线的用户信息
* username:num
*/
@Override
public void cacheLoginInfo(JwtToken jwtToken, LoginSysUserVo loginSysUserVo) {
if (jwtToken == null) {
throw new IllegalArgumentException("jwtToken不能为空");
}
if (loginSysUserVo == null) {
throw new IllegalArgumentException("loginSysUserVo不能为空");
}
// token
String token = jwtToken.getToken();
// 盐值
String salt = jwtToken.getSalt();
// 登录用户名称
String username = loginSysUserVo.getUsername();
// token md5值
String tokenMd5 = DigestUtils.md5Hex(token);
// Redis缓存JWT Token信息
JwtTokenRedisVo jwtTokenRedisVo = ShiroMapstructConvert.INSTANCE.jwtTokenToJwtTokenRedisVo(jwtToken);
// 用户客户端信息
ClientInfo clientInfo = ClientInfoUtil.get(HttpServletRequestUtil.getRequest());
// Redis缓存登录用户信息
// 将LoginSysUserVo对象复制到LoginSysUserRedisVo,使用mapstruct进行对象属性复制
LoginSysUserRedisVo loginSysUserRedisVo = LoginSysUserVoConvert.INSTANCE.voToRedisVo(loginSysUserVo);
loginSysUserRedisVo.setSalt(salt);
loginSysUserRedisVo.setClientInfo(clientInfo);
// Redis过期时间与JwtToken过期时间一致
Duration expireDuration = Duration.ofSeconds(jwtToken.getExpireSecond());
// 判断是否启用单个用户登录,如果是,这每个用户只有一个有效token
boolean singleLogin = jwtProperties.isSingleLogin();
if (singleLogin) {
deleteUserAllCache(username);
}
// 1. tokenMd5:jwtTokenRedisVo
String loginTokenRedisKey = String.format(CommonRedisKey.LOGIN_TOKEN, tokenMd5);
redisTemplate.opsForValue().set(loginTokenRedisKey, jwtTokenRedisVo, expireDuration);
// 2. username:loginSysUserRedisVo
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_USER, username), loginSysUserRedisVo, expireDuration);
// 3. salt hash,方便获取盐值鉴权
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_SALT, username), salt, expireDuration);
// 4. login user token
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, tokenMd5), loginTokenRedisKey, expireDuration);
}
@Override
public void refreshLoginInfo(String oldToken, String username, JwtToken newJwtToken) {
// 获取缓存的登录用户信息
LoginSysUserRedisVo loginSysUserRedisVo = getLoginSysUserRedisVo(username);
// 删除之前的token信息
deleteLoginInfo(oldToken, username);
// 缓存登录信息
cacheLoginInfo(newJwtToken, loginSysUserRedisVo);
}
@Override
public LoginSysUserRedisVo getLoginSysUserRedisVo(String username) {
if (StringUtils.isBlank(username)) {
throw new IllegalArgumentException("username不能为空");
}
return (LoginSysUserRedisVo) redisTemplate.opsForValue().get(String.format(CommonRedisKey.LOGIN_USER, username));
}
@Override
public LoginSysUserVo getLoginSysUserVo(String username) {
if (StringUtils.isBlank(username)) {
throw new IllegalArgumentException("username不能为空");
}
LoginSysUserRedisVo userRedisVo = getLoginSysUserRedisVo(username);
return userRedisVo;
}
@Override
public String getSalt(String username) {
if (StringUtils.isBlank(username)) {
throw new IllegalArgumentException("username不能为空");
}
String salt = (String) redisTemplate.opsForValue().get(String.format(CommonRedisKey.LOGIN_SALT, username));
return salt;
}
@Override
public void deleteLoginInfo(String token, String username) {
if (token == null) {
throw new IllegalArgumentException("token不能为空");
}
if (username == null) {
throw new IllegalArgumentException("username不能为空");
}
String tokenMd5 = DigestUtils.md5Hex(token);
// 1. delete tokenMd5
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_TOKEN, tokenMd5));
// 2. delete username
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_USER, username));
// 3. delete salt
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_SALT, username));
// 4. delete user token
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, tokenMd5));
}
@Override
public boolean exists(String token) {
if (token == null) {
throw new IllegalArgumentException("token不能为空");
}
String tokenMd5 = DigestUtils.md5Hex(token);
Object object = redisTemplate.opsForValue().get(String.format(CommonRedisKey.LOGIN_TOKEN, tokenMd5));
return object != null;
}
@Override
public void deleteUserAllCache(String username) {
Set<String> userTokenMd5Set = redisTemplate.keys(String.format(CommonRedisKey.LOGIN_USER_ALL_TOKEN, username));
if (CollectionUtils.isEmpty(userTokenMd5Set)) {
return;
}
// 1. 删除登录用户的所有token信息
List<String> tokenMd5List = redisTemplate.opsForValue().multiGet(userTokenMd5Set);
redisTemplate.delete(tokenMd5List);
// 2. 删除登录用户的所有user:token信息
redisTemplate.delete(userTokenMd5Set);
// 3. 删除登录用户信息
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_USER, username));
// 4. 删除登录用户盐值信息
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_SALT, username));
}
}
package io.geekidea.springbootplus.framework.shiro.cache.impl;
import io.geekidea.springbootplus.config.constant.CommonRedisKey;
import io.geekidea.springbootplus.config.properties.JwtProperties;
import io.geekidea.springbootplus.framework.common.bean.ClientInfo;
import io.geekidea.springbootplus.framework.shiro.cache.MerchantLoginRedisService;
import io.geekidea.springbootplus.framework.shiro.convert.LoginSysUserVoConvert;
import io.geekidea.springbootplus.framework.shiro.convert.ShiroMapstructConvert;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtToken;
import io.geekidea.springbootplus.framework.shiro.vo.JwtTokenRedisVo;
import io.geekidea.springbootplus.framework.shiro.vo.LoginSysUserRedisVo;
import io.geekidea.springbootplus.framework.shiro.vo.LoginSysUserVo;
import io.geekidea.springbootplus.framework.util.ClientInfoUtil;
import io.geekidea.springbootplus.framework.util.HttpServletRequestUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.List;
import java.util.Set;
@Service
public class MerchantLoginRedisServiceImpl implements MerchantLoginRedisService {
@Autowired
private JwtProperties jwtProperties;
@Autowired
private RedisTemplate redisTemplate;
/**
* key-value: 有过期时间-->token过期时间
* 1. tokenMd5:jwtTokenRedisVo
* 2. username:loginSysUserRedisVo
* 3. username:salt
* hash: 没有过期时间,统计在线的用户信息
* username:num
*/
@Override
public void cacheLoginInfo(JwtToken jwtToken, LoginSysUserVo loginSysUserVo) {
if (jwtToken == null) {
throw new IllegalArgumentException("jwtToken不能为空");
}
if (loginSysUserVo == null) {
throw new IllegalArgumentException("loginSysUserVo不能为空");
}
// token
String token = jwtToken.getToken();
// 盐值
String salt = jwtToken.getSalt();
// 登录用户名称
String username = loginSysUserVo.getUsername();
// token md5值
String tokenMd5 = DigestUtils.md5Hex(token);
// Redis缓存JWT Token信息
JwtTokenRedisVo jwtTokenRedisVo = ShiroMapstructConvert.INSTANCE.jwtTokenToJwtTokenRedisVo(jwtToken);
// 用户客户端信息
ClientInfo clientInfo = ClientInfoUtil.get(HttpServletRequestUtil.getRequest());
// Redis缓存登录用户信息
// 将LoginSysUserVo对象复制到LoginSysUserRedisVo,使用mapstruct进行对象属性复制
LoginSysUserRedisVo loginSysUserRedisVo = LoginSysUserVoConvert.INSTANCE.voToRedisVo(loginSysUserVo);
loginSysUserRedisVo.setSalt(salt);
loginSysUserRedisVo.setClientInfo(clientInfo);
// Redis过期时间与JwtToken过期时间一致
Duration expireDuration = Duration.ofSeconds(jwtToken.getExpireSecond());
// 判断是否启用单个用户登录,如果是,这每个用户只有一个有效token
boolean singleLogin = jwtProperties.isSingleLogin();
if (singleLogin) {
deleteUserAllCache(username);
}
// 1. tokenMd5:jwtTokenRedisVo
String loginTokenRedisKey = String.format(CommonRedisKey.LOGIN_TOKEN, tokenMd5);
redisTemplate.opsForValue().set(loginTokenRedisKey, jwtTokenRedisVo, expireDuration);
// 2. username:loginSysUserRedisVo
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_USER, username), loginSysUserRedisVo, expireDuration);
// 3. salt hash,方便获取盐值鉴权
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_SALT, username), salt, expireDuration);
// 4. login user token
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, tokenMd5), loginTokenRedisKey, expireDuration);
}
@Override
public void refreshLoginInfo(String oldToken, String username, JwtToken newJwtToken) {
// 获取缓存的登录用户信息
LoginSysUserRedisVo loginSysUserRedisVo = getLoginSysUserRedisVo(username);
// 删除之前的token信息
deleteLoginInfo(oldToken, username);
// 缓存登录信息
cacheLoginInfo(newJwtToken, loginSysUserRedisVo);
}
@Override
public LoginSysUserRedisVo getLoginSysUserRedisVo(String username) {
if (StringUtils.isBlank(username)) {
throw new IllegalArgumentException("username不能为空");
}
return (LoginSysUserRedisVo) redisTemplate.opsForValue().get(String.format(CommonRedisKey.LOGIN_USER, username));
}
@Override
public LoginSysUserVo getLoginSysUserVo(String username) {
if (StringUtils.isBlank(username)) {
throw new IllegalArgumentException("username不能为空");
}
LoginSysUserRedisVo userRedisVo = getLoginSysUserRedisVo(username);
return userRedisVo;
}
@Override
public String getSalt(String username) {
if (StringUtils.isBlank(username)) {
throw new IllegalArgumentException("username不能为空");
}
String salt = (String) redisTemplate.opsForValue().get(String.format(CommonRedisKey.LOGIN_SALT, username));
return salt;
}
@Override
public void deleteLoginInfo(String token, String username) {
if (token == null) {
throw new IllegalArgumentException("token不能为空");
}
if (username == null) {
throw new IllegalArgumentException("username不能为空");
}
String tokenMd5 = DigestUtils.md5Hex(token);
// 1. delete tokenMd5
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_TOKEN, tokenMd5));
// 2. delete username
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_USER, username));
// 3. delete salt
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_SALT, username));
// 4. delete user token
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, tokenMd5));
}
@Override
public boolean exists(String token) {
if (token == null) {
throw new IllegalArgumentException("token不能为空");
}
String tokenMd5 = DigestUtils.md5Hex(token);
Object object = redisTemplate.opsForValue().get(String.format(CommonRedisKey.LOGIN_TOKEN, tokenMd5));
return object != null;
}
@Override
public void deleteUserAllCache(String username) {
Set<String> userTokenMd5Set = redisTemplate.keys(String.format(CommonRedisKey.LOGIN_USER_ALL_TOKEN, username));
if (CollectionUtils.isEmpty(userTokenMd5Set)) {
return;
}
// 1. 删除登录用户的所有token信息
List<String> tokenMd5List = redisTemplate.opsForValue().multiGet(userTokenMd5Set);
redisTemplate.delete(tokenMd5List);
// 2. 删除登录用户的所有user:token信息
redisTemplate.delete(userTokenMd5Set);
// 3. 删除登录用户信息
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_USER, username));
// 4. 删除登录用户盐值信息
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_SALT, username));
}
}
......@@ -19,7 +19,7 @@ package io.geekidea.springbootplus.framework.shiro.cache.impl;
import io.geekidea.springbootplus.config.constant.CommonRedisKey;
import io.geekidea.springbootplus.config.properties.JwtProperties;
import io.geekidea.springbootplus.framework.common.bean.ClientInfo;
import io.geekidea.springbootplus.framework.shiro.cache.LoginRedisService;
import io.geekidea.springbootplus.framework.shiro.cache.SysLoginRedisService;
import io.geekidea.springbootplus.framework.shiro.convert.LoginSysUserVoConvert;
import io.geekidea.springbootplus.framework.shiro.convert.ShiroMapstructConvert;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtToken;
......@@ -47,7 +47,7 @@ import java.util.Set;
* @since 1.3.0.RELEASE
**/
@Service
public class LoginRedisServiceImpl implements LoginRedisService {
public class SysLoginRedisServiceImpl implements SysLoginRedisService {
@Autowired
private JwtProperties jwtProperties;
......
......@@ -19,7 +19,7 @@ package io.geekidea.springbootplus.framework.shiro.jwt;
import io.geekidea.springbootplus.config.properties.JwtProperties;
import io.geekidea.springbootplus.framework.common.api.ApiCode;
import io.geekidea.springbootplus.framework.common.api.ApiResult;
import io.geekidea.springbootplus.framework.shiro.cache.LoginRedisService;
import io.geekidea.springbootplus.framework.shiro.cache.SysLoginRedisService;
import io.geekidea.springbootplus.framework.shiro.service.ShiroLoginService;
import io.geekidea.springbootplus.framework.shiro.util.JwtTokenUtil;
import io.geekidea.springbootplus.framework.shiro.util.JwtUtil;
......@@ -49,11 +49,11 @@ public class JwtFilter extends AuthenticatingFilter {
private final ShiroLoginService shiroLoginService;
private final LoginRedisService loginRedisService;
private final SysLoginRedisService loginRedisService;
private final JwtProperties jwtProperties;
public JwtFilter(ShiroLoginService shiroLoginService, LoginRedisService loginRedisService, JwtProperties jwtProperties) {
public JwtFilter(ShiroLoginService shiroLoginService, SysLoginRedisService loginRedisService, JwtProperties jwtProperties) {
this.shiroLoginService = shiroLoginService;
this.loginRedisService = loginRedisService;
this.jwtProperties = jwtProperties;
......
/*
* Copyright 2019-2029 geekidea(https://github.com/geekidea)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.geekidea.springbootplus.framework.shiro.jwt;
import io.geekidea.springbootplus.framework.shiro.cache.LoginRedisService;
import io.geekidea.springbootplus.framework.shiro.vo.LoginSysUserRedisVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.SetUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
/**
* Shiro 授权认证-系统
*
* @author geekidea
* @date 2019-09-27
* @since 1.3.0.RELEASE
**/
@Slf4j
public class JwtRealmSystem extends AuthorizingRealm {
private LoginRedisService loginRedisService;
public JwtRealmSystem(LoginRedisService loginRedisService) {
this.loginRedisService = loginRedisService;
}
@Override
public boolean supports(AuthenticationToken token) {
return token != null && token instanceof JwtToken;
}
/**
* 授权认证,设置角色/权限信息
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.debug("doGetAuthorizationInfo principalCollection...");
// 设置角色/权限信息
JwtToken jwtToken = (JwtToken) principalCollection.getPrimaryPrincipal();
// 获取username
String username = jwtToken.getUsername();
// 获取登录用户角色权限信息
LoginSysUserRedisVo loginSysUserRedisVo = loginRedisService.getLoginSysUserRedisVo(username);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 设置角色
authorizationInfo.setRoles(SetUtils.hashSet(loginSysUserRedisVo.getRoleCode()));
// 设置权限
authorizationInfo.setStringPermissions(loginSysUserRedisVo.getPermissionCodes());
return authorizationInfo;
}
/**
* 登录认证
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.debug("doGetAuthenticationInfo authenticationToken...");
// 校验token
JwtToken jwtToken = (JwtToken) authenticationToken;
if (jwtToken == null) {
throw new AuthenticationException("jwtToken不能为空");
}
String salt = jwtToken.getSalt();
if (StringUtils.isBlank(salt)) {
throw new AuthenticationException("salt不能为空");
}
return new SimpleAuthenticationInfo(
jwtToken,
salt,
getName()
);
}
}
......@@ -16,7 +16,7 @@
package io.geekidea.springbootplus.framework.shiro.jwt.realm;
import io.geekidea.springbootplus.framework.shiro.cache.LoginRedisService;
import io.geekidea.springbootplus.framework.shiro.cache.AppLoginRedisService;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtToken;
import io.geekidea.springbootplus.framework.shiro.vo.LoginSysUserRedisVo;
import lombok.extern.slf4j.Slf4j;
......@@ -41,9 +41,9 @@ import org.apache.shiro.subject.PrincipalCollection;
@Slf4j
public class JwtRealmAppUser extends AuthorizingRealm {
private LoginRedisService loginRedisService;
private AppLoginRedisService loginRedisService;
public JwtRealmAppUser(LoginRedisService loginRedisService) {
public JwtRealmAppUser(AppLoginRedisService loginRedisService) {
this.loginRedisService = loginRedisService;
}
......
......@@ -14,9 +14,10 @@
* limitations under the License.
*/
package io.geekidea.springbootplus.framework.shiro.jwt;
package io.geekidea.springbootplus.framework.shiro.jwt.realm;
import io.geekidea.springbootplus.framework.shiro.cache.LoginRedisService;
import io.geekidea.springbootplus.framework.shiro.cache.MerchantLoginRedisService;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtToken;
import io.geekidea.springbootplus.framework.shiro.vo.LoginSysUserRedisVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.SetUtils;
......@@ -40,9 +41,9 @@ import org.apache.shiro.subject.PrincipalCollection;
@Slf4j
public class JwtRealmMerchant extends AuthorizingRealm {
private LoginRedisService loginRedisService;
private MerchantLoginRedisService loginRedisService;
public JwtRealmMerchant(LoginRedisService loginRedisService) {
public JwtRealmMerchant(MerchantLoginRedisService loginRedisService) {
this.loginRedisService = loginRedisService;
}
......
......@@ -14,9 +14,10 @@
* limitations under the License.
*/
package io.geekidea.springbootplus.framework.shiro.jwt;
package io.geekidea.springbootplus.framework.shiro.jwt.realm;
import io.geekidea.springbootplus.framework.shiro.cache.LoginRedisService;
import io.geekidea.springbootplus.framework.shiro.cache.SysLoginRedisService;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtToken;
import io.geekidea.springbootplus.framework.shiro.vo.LoginSysUserRedisVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.SetUtils;
......@@ -38,11 +39,11 @@ import org.apache.shiro.subject.PrincipalCollection;
* @since 1.3.0.RELEASE
**/
@Slf4j
public class JwtRealm extends AuthorizingRealm {
public class JwtRealmSystem extends AuthorizingRealm {
private LoginRedisService loginRedisService;
private SysLoginRedisService loginRedisService;
public JwtRealm(LoginRedisService loginRedisService) {
public JwtRealmSystem(SysLoginRedisService loginRedisService) {
this.loginRedisService = loginRedisService;
}
......
......@@ -19,7 +19,7 @@ package io.geekidea.springbootplus.framework.shiro.service.impl;
import io.geekidea.springbootplus.config.constant.CommonConstant;
import io.geekidea.springbootplus.config.properties.JwtProperties;
import io.geekidea.springbootplus.config.properties.SpringBootPlusProperties;
import io.geekidea.springbootplus.framework.shiro.cache.LoginRedisService;
import io.geekidea.springbootplus.framework.shiro.cache.SysLoginRedisService;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtToken;
import io.geekidea.springbootplus.framework.shiro.service.ShiroLoginService;
import io.geekidea.springbootplus.framework.shiro.util.JwtTokenUtil;
......@@ -50,7 +50,7 @@ public class ShiroLoginServiceImpl implements ShiroLoginService {
@Lazy
@Autowired
private LoginRedisService loginRedisService;
private SysLoginRedisService loginRedisService;
@Lazy
@Autowired
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment