最近在工作中用到权限框架shiro用于多重身份认证和登录,并且需要与sprinboot整合,于是网上搜索参考各种资料,再问问大佬,算是搞出来 但是估计还是有坑没有被发现
需求:现在客户登录分为工程商和工程师两种客户,并且这两种客户对于2张表,需要shiro来区别登录身份,并且去不同表查询用户信息来加以验证
步骤:
1:需要自定义UserNamePasswordToken
2 定义工程师和工程商的realm,并且实现认证和授权 ,这里只看认证,授权请参考:sprinboot与shiro多realm授权
Reaml1:
package com.goodits.gositsmanager.engineeringprovider.authc;
import com.goodits.gositsmanager.engineeringprovider.common.pub.PubGlobal;
import com.goodits.gositsmanager.engineeringprovider.module.system.user.dao.entity.PersonInfo;
import com.goodits.gositsmanager.engineeringprovider.module.system.user.dao.entity.User;
import com.goodits.gositsmanager.engineeringprovider.module.system.user.dao.mapper.PersonInfoMapper;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
/**
* @author chenzhi
* @Description: 工程师 认证
* @Date:Created: in 14:36 2018/8/13
* @Modified by:
*/
public class EngineerRealm extends AuthorizingRealm {
@Autowired
private PersonInfoMapper personInfoMapper;
/**
* 认证
*
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
MyUsernamePasswordToken mytoken = (MyUsernamePasswordToken) token;
String loginName = (String) mytoken.getPrincipal();
List list = personInfoMapper.getLoginInfo(loginName);
if (list == null || list.size() == 0) {
// 用户名不存在
throw new UnknownAccountException();
}
if (list.size() > 1) {
throw new AuthenticationException();
}
User user = list.get(0);
if (user == null) {
// 用户名不存在
throw new UnknownAccountException();
}
//工程师
user.setIdentify("2");
return new SimpleAuthenticationInfo(
user, // 用户
user.getPassword(), // 密码
getName() // realm name
);
//如果需要盐值加密:假设盐值为String salt = "qwer123";
// 则应该返回 return new SimpleAuthenticationInfo(user,user.getPassword,salt,getName)
//shiro会将查询的user和密码交给后面的HashedCredentialsMatcher密码匹配器根据加密规则进行密码匹配验证
}
/**
* 授权
*
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getSession(false).getAttribute(PubGlobal.LOGIN_INFO);
if (null != user) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
String userType = String.valueOf(1);
if (null == userType) {
return null;
}
info.addRole(userType);
return info;
}
return null;
}
}
Reaml2:与Realm1类似的,这里贴一部分:
public class ProviderRealm extends AuthorizingRealm {
@Autowired
private UserMapper userMapper;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
MyUsernamePasswordToken mytoken = (MyUsernamePasswordToken) token;
String loginName = (String) token.getPrincipal();
Map param = new HashMap();
param.put("loginName", loginName);
List infoList = userMapper.getUserInfo(param);
if (infoList == null || infoList.isEmpty()) {
throw new UnknownAccountException();// 用户名不存在
}
if (infoList.size() > 1) {
throw new AuthenticationException();
}
User user = infoList.get(0);
if (user == null) {
throw new UnknownAccountException();// 用户名不存在
}
//工程商
user.setIdentify("1");
// 交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配
return new SimpleAuthenticationInfo(
user, // 用户
user.getPassword(), // 密码
getName() // 获得realm的name 这里就是ProviderRealm
);
}
//授权省略
3 :自定义Realm管理器 管理多个Realm
package com.goodits.gositsmanager.engineeringprovider.authc;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author chenzhi
* @Description: 自定义当使用多realm时管理器
* @Date:Created: in 13:41 2018/8/13
* @Modified by:
*/
public class MyModularRealmAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) {
//先判断Realm是否为空
assertRealmsConfigured();
//强转为自定义的Token
MyUsernamePasswordToken myUsernamePasswordToken = (MyUsernamePasswordToken) authenticationToken;
//拿到登录类型
String loginType = myUsernamePasswordToken.getLoginType();
//拿到所有Realm集合
Collection realms = getRealms();
List myrealms = new ArrayList<>();
//遍历每个realm 根据loginType将对应的Reaml加入到myrealms
for (Realm realm : realms) {
//拿到Realm的类名 ,所以在定义Realm时,类名要唯一标识并且包含枚举中loginType某一个Type
//注意:一个Realm的类名不能包含有两个不同的loginType
if (realm.getName().contains(loginType)) {
myrealms.add(realm);
}
}
//判断是单Reaml还是多Realm
if (myrealms.size() == 1) {
return doSingleRealmAuthentication(myrealms.iterator().next(), myUsernamePasswordToken);
} else {
return doMultiRealmAuthentication(myrealms, myUsernamePasswordToken);
}
}
}
注意:定义的Realm的类名一定要包含枚举类中设置的值
4:定义枚举类:
package com.goodits.gositsmanager.engineeringprovider.module.system.user.pojo;
/**
* @author chenzhi
* @Description: 定义枚举类 用来区别是服务商还是工程师
* @Date:Created: in 11:12 2018/8/13
* @Modified by:
*/
public enum LoginType {
PROVIDER("Provider"),ENGINEER("Engineer");
private String type;
private LoginType(String type) {
this.type = type;
}
@Override
public String toString() {
return this.type.toString();
}
}
5:在sprinboot中设置shiroConfig
6:在shiroConfig注册密码匹配器,设置密码验证匹配规则(盐值不需要手动设置,只要你的SimpleAuthenticationInfo带盐值到密码匹配器了,密码匹配器中的内部有个方法会自动获取盐值并且进行加密匹配验证)
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("MD5"); //加密方式为MD5
credentialsMatcher.setHashIterations(3); //加密次数为3次
credentialsMatcher.setStoredCredentialsHexEncoded(true);//采用hash散列算法加密
return credentialsMatcher;
}
shrio中密码验证:可以看到盐值是从AuthenticationInfo获取盐值,也就是你在reaml中返回的AuthenticationInfo如果有设置盐值,这个方法就可以获取到
7:在shiroConfig注册两个realm,并且将注册好的密码匹配管理器设置进去
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public ProviderRealm providerRealm() {
ProviderRealm realm = new ProviderRealm();
realm.setCredentialsMatcher(hashedCredentialsMatcher()); //设置密码匹配管理器
realm.setCachingEnabled(true);
return realm;
}
//注册realm
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public EngineerRealm engineerRealm() {
EngineerRealm realm = new EngineerRealm();
realm.setCredentialsMatcher(hashedCredentialsMatcher());
realm.setCachingEnabled(true);
return realm;
}
8:在shiroConfig注册自定义的realm管理器
//注册配置自定义的Realm管理
@Bean
public MyModularRealmAuthenticator userModularRealmAuthenticator() {
MyModularRealmAuthenticator myModularRealmAuthenticator = new MyModularRealmAuthenticator();
//设置判断realm条件 FirstSuccessfulStrategy只要有一个realm成功就不会验证其他的了
myModularRealmAuthenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
return myModularRealmAuthenticator;
}
9:在shiroConfig将所有realm放到shiro的安全管理器中
@Bean(name = "securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSessionManager(sessionManager());
securityManager.setCacheManager(cacheManager());
//设置使用自定义的realm的管理器
securityManager.setAuthenticator(userModularRealmAuthenticator());
//将所有的realm交给安全管理器
List realmList = new ArrayList<>();
realmList.add(providerRealm());
realmList.add(engineerRealm());
securityManager.setRealms(realmList);
return securityManager;
}
10配置shiro拦截规则:注意假如拦截规则必须使用LinkedhashMap;保证拦截顺序,为什么这样做,我也不是太清楚???哪位知道的希望解惑一下哈
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
Map filterChainDefinitionMap = new LinkedHashMap();
filterChainDefinitionMap.put("/swagger**", "anon");
filterChainDefinitionMap.put("/rest/server/*/base/**", "anon");
//filterChainDefinitionMap.put("/rest/server/**", "authc");
// filterChainDefinitionMap.put("/rest/server/**", "authc");
filterChainDefinitionMap.put("/rest/server/**", "anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
shiroFilterFactoryBean.setLoginUrl("/403");
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
return shiroFilterFactoryBean;
}
11 在Controler层进行验证登录:与之无关的可以忽略掉
@ApiOperation(notes = "登录", value = "用户登录操作", httpMethod = "POST")
@RequestMapping(value = "/base/login", method = RequestMethod.POST)
public RestResponse login(@Valid @RequestBody LoginReq req) throws UnsupportedEncodingException {
RestResponse restResponse = new RestResponse();
// 密码需要先经过Base64转码,由于前台传过来的密码是base64加密的,所以这里需要进行解密
String originalPwd = Base64Util.getFromBase64(req.getPassword());
// UsernamePasswordToken token = new UsernamePasswordToken(req.getLoginName(), originalPwd);
String loginType = null;
//工程商
if ("1".equals(req.getIdentity())) {
loginType = LoginType.PROVIDER.toString();
//工程师
} else if ("2".equals(req.getIdentity())) {
loginType = LoginType.ENGINEER.toString();
}
MyUsernamePasswordToken token = new MyUsernamePasswordToken(req.getLoginName(), originalPwd, loginType);
Subject currentUser = SecurityUtils.getSubject();
try {
currentUser.login(token);
User user = (User) currentUser.getPrincipal();
int personID = user.getPersonID();
if (req.getIdentity().equals("1")) {
// 工程商 保存客户端ID
userService.updateClientID(personID, req.getClientID());
} else if (req.getIdentity().equals("2")) {
personInfoService.updateClientID(personID, req.getClientID());
}
} catch (UnknownAccountException ex) {
throw new GOSITSManagerException(ErrorCode.USER_NOT_EXIST);
} catch (IncorrectCredentialsException ex) {
throw new GOSITSManagerException(ErrorCode.USER_PWD_ERROR);
} catch (AuthenticationException ex) {
throw new GOSITSManagerException(ErrorCode.LOGIN_FAIL);
}
if (currentUser.isAuthenticated()) {
Session session = currentUser.getSession(true);
//密码设置不可见
User user = (User) currentUser.getPrincipal();
user.setPassword("*****");
session.setAttribute(PubGlobal.LOGIN_INFO, user);
restResponse.setDatas(user);
// 测试
//设置登录过期时间 1s过期
// SecurityUtils.getSubject().getSession().setTimeout(10000);
// User principal = (User) currentUser.getPrincipal();
// System.out.println(principal.getPersonName());
} else {
token.clear();
throw new GOSITSManagerException(ErrorCode.LOGIN_FAIL);
}
return restResponse;
}
12最后贴一下:pom需要的maven依赖
org.apache.shiro
shiro-core
${shiro.version}
org.apache.shiro
shiro-spring
${shiro.version}
org.apache.shiro
shiro-cas
${shiro.version}
commons-codec
commons-codec
20041127.091804
有什么不对的地方希望大家提出来,我也会持续更新
再贴上整个ShiroConfig
package com.goodits.gositsmanager.engineeringprovider.config;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.goodits.gositsmanager.engineeringprovider.authc.EngineerRealm;
import com.goodits.gositsmanager.engineeringprovider.authc.MyModularRealmAuthenticator;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.realm.Realm;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.env.Environment;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.ShiroHttpSession;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.crazycake.shiro.RedisCacheManager;
import com.goodits.gositsmanager.engineeringprovider.authc.ProviderRealm;
@Configuration
public class ShiroConfig implements EnvironmentAware {
private RelaxedPropertyResolver propertyResolver;
@Override
public void setEnvironment(Environment env) {
this.propertyResolver = new RelaxedPropertyResolver(env, "spring.redis.");
}
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(propertyResolver.getProperty("host"));
;
redisManager.setPort(Integer.parseInt(propertyResolver.getProperty("port")));
redisManager.setPassword(propertyResolver.getProperty("password"));
redisManager.setTimeout(Integer.parseInt(propertyResolver.getProperty("timeout")));
redisManager.setExpire(Integer.parseInt(propertyResolver.getProperty("expire")));
return redisManager;
}
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultWebSessionManager sessionManager() {
final DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
Cookie cookie = new SimpleCookie(java.util.UUID.randomUUID() + ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
sessionManager.setSessionIdCookie(cookie);
sessionManager.setGlobalSessionTimeout(3600000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("MD5");
credentialsMatcher.setHashIterations(3);
credentialsMatcher.setStoredCredentialsHexEncoded(true);
return credentialsMatcher;
}
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public ProviderRealm providerRealm() {
ProviderRealm realm = new ProviderRealm();
realm.setCredentialsMatcher(hashedCredentialsMatcher());
realm.setCachingEnabled(true);
return realm;
}
//注册realm
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public EngineerRealm engineerRealm() {
EngineerRealm realm = new EngineerRealm();
realm.setCredentialsMatcher(hashedCredentialsMatcher());
realm.setCachingEnabled(true);
return realm;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSessionManager(sessionManager());
securityManager.setCacheManager(cacheManager());
//设置使用自定义的realm的管理器
securityManager.setAuthenticator(userModularRealmAuthenticator());
//将所有的realm交给安全管理器
List realmList = new ArrayList<>();
realmList.add(providerRealm());
realmList.add(engineerRealm());
securityManager.setRealms(realmList);
return securityManager;
}
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
Map filterChainDefinitionMap = new LinkedHashMap();
filterChainDefinitionMap.put("/swagger**", "anon");
filterChainDefinitionMap.put("/rest/server/*/base/**", "anon");
//filterChainDefinitionMap.put("/rest/server/**", "authc");
// filterChainDefinitionMap.put("/rest/server/**", "authc");
filterChainDefinitionMap.put("/rest/server/**", "anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
shiroFilterFactoryBean.setLoginUrl("/403");
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
return shiroFilterFactoryBean;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
daap.setProxyTargetClass(true);
return daap;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(securityManager());
return aasa;
}
//注册配置自定义的Realm管理
@Bean
public MyModularRealmAuthenticator userModularRealmAuthenticator() {
MyModularRealmAuthenticator myModularRealmAuthenticator = new MyModularRealmAuthenticator();
//设置判断realm条件 FirstSuccessfulStrategy只要有一个realm成功就不会验证其他的了
myModularRealmAuthenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
return myModularRealmAuthenticator;
}
}