圖一: shiro整體架構功能
圖二: shiro架構API實現方式
@DependsOn(value="springUtils")
@Configuration
public class ShiroCoreConfig {
@Autowired
private RedisProperties redisParam;
public static final String CACHE_KEY_PREFIX = "cache:key:";
public static final String SESSION_KEY_PREFIX = "session:key:";
public static final int SESSION_EXPIRE = 60*60;
public static final int CACHE_EXPIRE = 60*60;
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/api/s/sys/login", "anon");
filterChainDefinitionMap.put("/api/s/sys/logout", "anon");
filterChainDefinitionMap.put("/api/sys/**", "authc");
shiroFilterFactoryBean.setLoginUrl("/api/s/sys/unauth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager(CoreRealm coreRealm,
RedisCacheManager redisCacheManager,
SessionManager sessionManager,
CookieRememberMeManager rememberMeManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setCacheManager(redisCacheManager);
securityManager.setRealm(coreRealm);
securityManager.setSessionManager(sessionManager);
securityManager.setRememberMeManager(rememberMeManager);
return securityManager;
}
@Bean
public SimpleCookie simpleCookie() {
SimpleCookie simpleCookie = new SimpleCookie("RememberMe_Cookie");
simpleCookie.setHttpOnly(true);
simpleCookie.setMaxAge(60*60*24*30);
return simpleCookie;
}
@Bean
public CookieRememberMeManager rememberMeManager(SimpleCookie simpleCookie) {
CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
rememberMeManager.setCookie(simpleCookie);
//cipherKey是加密rememberMe Cookie的密鑰
rememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
return rememberMeManager;
}
@Bean
public HashedCredentialsMatcher credentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME);
credentialsMatcher.setHashIterations(SHA256Util.HASH_ITERATIONS);
return credentialsMatcher;
}
@Bean
public CoreRealm coreRealm(HashedCredentialsMatcher credentialsMatcher) {
CoreRealm realm = new CoreRealm();
realm.setCredentialsMatcher(credentialsMatcher);
return realm;
}
@Bean
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(redisParam.getHost()+":"+redisParam.getPort());
redisManager.setPassword(redisParam.getPassword());
redisManager.setTimeout(redisParam.getTimeout());
redisManager.setDatabase(redisParam.getDatabase());
return redisManager;
}
@Bean
public RedisSessionIdGenerator redisSessionIdGenerator() {
return new RedisSessionIdGenerator();
}
@Bean
public RedisSessionDAO redisSessionDAO(RedisManager redisManager,RedisSessionIdGenerator redisSessionIdGenerator) {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setSessionIdGenerator(redisSessionIdGenerator);
redisSessionDAO.setRedisManager(redisManager);
redisSessionDAO.setKeyPrefix(SESSION_KEY_PREFIX);
redisSessionDAO.setExpire(SESSION_EXPIRE);
return redisSessionDAO;
}
@Bean
public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {
RedisSessionManager manager = new RedisSessionManager();
manager.setSessionDAO(redisSessionDAO);
return manager;
}
@Bean
public RedisCacheManager redisCacheManager(RedisManager redisManager) {
RedisCacheManager cacheManager = new RedisCacheManager();
cacheManager.setRedisManager(redisManager);
cacheManager.setKeyPrefix(CACHE_KEY_PREFIX);
cacheManager.setExpire(CACHE_EXPIRE);
cacheManager.setPrincipalIdFieldName("username");
return cacheManager;
}
}
核心代碼: 配置SecurityManager(囊括所有核心模塊)
public class CoreRealm extends AuthorizingRealm {
private ManagerService service = SpringUtils.getBean(ManagerService.class);
private SysRoleRep roleRep = SpringUtils.getBean(SysRoleRep.class);
private SysPermissionRep permissionRep = SpringUtils.getBean(SysPermissionRep.class);
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
log.info("Authorization......");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
String principal = (String) principals.getPrimaryPrincipal();
List<SysRole> roles = roleRep.rolesByUsername(principal);
List<String> roleList = roles.stream().map(SysRole::getRoleTargetId).collect(Collectors.toList());
List<String> permissionList = permissionRep
.permissionsByUsername(roles.stream().map(SysRole::getId).collect(Collectors.toList()))
.stream().map(SysPermission::getPermissionTargetId).collect(Collectors.toList());
authorizationInfo.setRoles(new HashSet<String>(roleList));
authorizationInfo.setStringPermissions(new HashSet<String>(permissionList));
log.info("---------------------------------------");
log.info("授權用戶信息:{}",principal);
log.info("授權角色列表:{}",roleList);
log.info("授權權限列表:{}",permissionList);
log.info("---------------------------------------");
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
/*
* 業務邏輯:根據用戶名查看用戶信息
*/
SysUser sysUser = service.getUserByUsername(token.getPrincipal().toString());
if(sysUser == null || StringUtils.isBlank(sysUser.getId())) {
throw new AuthenticationException("用戶名或密碼錯誤!");
}
switch (sysUser.getState()) {
case 1:
throw new LockedAccountException("賬號已被鎖定,請到後臺處理!");
case 2:
throw new DisabledAccountException("賬號已被禁用,請規範登錄!");
default:
break;
}
String hashedCredentials = sysUser.getPassword();
ByteSource credentialsSalt = ByteSource.Util.bytes(sysUser.getUsername());
SimpleAuthenticationInfo authenticationInfo =
new SimpleAuthenticationInfo(token.getPrincipal(), hashedCredentials, credentialsSalt, getName());
// 單點登錄 : 刪除之前登錄人信息。(這裏的單點登錄不是多系統的單點登錄)
ShiroUtils.deleteCache(token.getPrincipal().toString(), true);
return authenticationInfo;
}
}
Realm使用樣例
//SessionId生長成器
public class RedisSessionIdGenerator implements SessionIdGenerator {
@Override
public Serializable generateId(Session session) {
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
return String.format(uuid);
}
}
//RedisSessionManager
public class RedisSessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "Authorization";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public RedisSessionManager() {
//重寫構造器
super();
this.setDeleteInvalidSessions(true);
}
@Override
protected Serializable getSessionId(ServletRequest request,ServletResponse response) {
String token = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
// 如果請求頭中存在token 則從請求頭中獲取token
if (!StringUtils.isEmpty(token)) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return token;
} else {
// 否則按默認規則從cookie取token
return super.getSessionId(request, response);
}
}
}
//開源shiro-redis支持
<!-- Shiro-redis插件 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.2.3</version>
</dependency>
SessionManager之RedisSessionManager。
@Slf4j
public class ShiroUtils {
private static RedisSessionDAO redisSessionDAO = SpringUtils.getBean(RedisSessionDAO.class);
/*
* 獲取session
*/
public static Session getSession() {
return SecurityUtils.getSubject().getSession();
}
// /*
// * 獲取當前用戶角色信息
// */
// public static SysRole getRole() {
// SysUserMapper userMapper = (SysUserMapper) SpringUtils.getBean(SysUserMapper.class);
// return userMapper.findRolesByUserId(getUser().getUserId());
// }
//
// /*
// * 獲取用戶信息
// */
// public static SysUser getUser() {
// return (SysUser)SecurityUtils.getSubject().getPrincipal();
// }
/*
* 登出
*/
public static void logout() {
SecurityUtils.getSubject().logout();
}
/*
* 刪除用戶緩存信息
*/
public static void deleteCache(String username,boolean isRemoveSession) {
Session session = null;
Collection<Session> sessions = redisSessionDAO.getActiveSessions();
String redisName = "";
Object attribute = null;
for(Session sessionInfo : sessions){
//遍歷Session,找到該用戶名稱對應的Session
log.info("redis中Session中的參數:{}",DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
attribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (attribute == null) {
continue;
}
log.info("redis中Session中參數的值:{}",attribute);
redisName = (String) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();
if (redisName == null) {
continue;
}
if (Objects.equals(redisName, username)) {
session=sessionInfo;
}
}
if (session == null||attribute == null) {
return;
}
//刪除session
if (isRemoveSession) {
log.info("刪除session:{}",session);
redisSessionDAO.delete(session);
}
//刪除Cache,在訪問受限接口時會重新授權
DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
Authenticator authc = securityManager.getAuthenticator();
((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute);
}
}
ShiroUtils:工具類(相關用戶信息、角色,根據使用的持久化框架實現)
/*
SpringUtils: 利用單個bean的裝載順序,實現ApplicationContextAware注入,如果涉及
依賴注入多個bean,使用@DependsOn先行注入依賴Bean。(這裏實現BeanPostProcessor的
目的也是想提前裝載SpringUtils,但是測試時發現注入還是不夠靠前,相對於不實現
BeanPostProcessor是有提前的。)
*/
@Component
public class SpringUtils implements ApplicationContextAware,BeanPostProcessor{
private static ApplicationContext ac;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
ac = applicationContext;
}
public static void cheakApplicationContext(){
if(ac==null)
throw new IllegalStateException("ApplicationContext is Emtey");
}
public static void clearApplicatinContext(){
ac = null;
}
public static <T> T getBean(Class<T> cla){
cheakApplicationContext();
return ac.getBean(cla);
}
public static <T> T getBean(String beanName){
cheakApplicationContext();
return (T) ac.getBean(beanName);
}
}
public class SHA256Util {
//加密名稱
public static final String HASH_ALGORITHM_NAME = "SHA-256";
//加密
public static final int HASH_ITERATIONS = 15;
public static String sha256(String password,String salt) {
return new SimpleHash(HASH_ALGORITHM_NAME, password, salt, HASH_ITERATIONS).toString();
}
public static void main(String[] args) {
String sha256 = sha256("123456", "admin");
System.out.println(sha256+"\n"+sha256.length());
}
}
兩個工具類。
小編這裏,主要是上手快速使用。(略有不足,歡迎小夥伴留言)
參考博主: SpringBoot整合Shiro