致谢首先感谢张开涛大神,写了很多有关shiro的博客,内容详实并佐以实例,使鄙人可以很快上手使用shiro,在这里给出大神的跟我学shiro的博客,方便自己跟他人浏览学习。 前言shiro的确很强大,可以在页面使用标签,也可以在控制层以注解的方式进行访问控制,只是做过网站的都知道,我们往往需要实现多种方式的登陆,例如:手机验证码登陆,QQ,微信登陆,等等,这个时候原生的shiro就无法达到这个功能了,作为一名java开发人员,最熟悉的就是封装/继承/多态了,OK,以手机验证码登陆为例,我们自己实现吧! 正文理论要进行多方式登陆 1.首先要区分用户使用的登陆方式 2.然后根据登陆方式选择我们注册到shiro中的某个匹配当前登陆方式的Realm,每个Realm中都能用来获取用户的认证信息以及权限信息,并根据这些信息判断用户是否有权利登陆系统以及访问哪些功能,那么就至少需要有两个以上的Realm提供给shiro的ModularRealmAuthenticator(模块化用户认证器) 3.shiro提供了三种策略来进行多Realm下认证:AllSuccessfulStrategy(全部匹配策略),AtLeastOneSuccessfulStrategy(至少一个匹配),FirstSuccessfulStrategy(第一个匹配),默认使用AtLeastOneSuccessfulStrategy,这些策略并符合我们的需要,因此重写ModularRealmAuthenticator 下面,了解一下需要继承的类/接口,以及需要重写的方法: UsernamePasswordToken
此类保存了用户的登陆信息,但是不足以满足多登陆方式的要求,因而需要进行一些调整,增加一个loginType属性,用来保存用户的登陆方式 FormAuthenticationFilter.createToken(ServletRequest request, ServletResponse response)
此类是用户提交表单进行登陆认证的过滤器,此方法是用来以为用户提交的信息合成一个Token(令牌),并拿着这个令牌去判断信息是否匹配 ModularRealmAuthenticator.doMultiRealmAuthentication(Collection realms, AuthenticationToken token)
此类是模块化凭证匹配器此方法是用来在注入的realms中按照指定的策略,逐个获取保存在系统中认证信息并整合在一起,返回这个整合后的信息 AuthenticatingRealm.supports(AuthenticationToken token)
此类是认证信息匹配器,此方法用来判断当前用户提交的登陆信息是否可以被校验,返回true则会使用该类的实现类进行匹配,返回false则跳过 代码创建UsernamePasswordLoginTypeToken,继承UsernamePasswordToken /**
* Created by Lancelot on 2017/3/17.
* 重写{@link #UsernamePasswordToken},增加{@link #loginType}属性,该属性是在登陆界面form表单中传递过来的,定义了用户使用的登陆类型
*/
public class UsernamePasswordLoginTypeToken extends UsernamePasswordToken {
private static final long serialVersionUID = 7134536615448037793L;
/**
*登陆类型
*/
private String loginType;
public UsernamePasswordLoginTypeToken(String username, String password, boolean rememberMe, String host, String loginType) {
super(username, password, rememberMe, host);
this.loginType = loginType;
}
public String getLoginType() {
return loginType;
}
public void setLoginType(String loginType) {
this.loginType = loginType;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
创建MyFormAuthenticationFilter,继承FormAuthenticationFilter,并重写createToken(): public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
public static final String DEFAULT_LOGIN_TYPE_PARAM = "loginType";
private boolean kickOutAfter = false;
private int maxSession = 1;
private CacheManager cacheManager;
private SessionManager sessionManager;
private String kickOutSessionCacheName;
private Cache<String, Deque<Serializable>> cache;
private ReentrantLock reentrantLock = new ReentrantLock();
private String loginTypeParamName = DEFAULT_LOGIN_TYPE_PARAM;
@PostConstruct
public void init() {
this.cache = cacheManager.getCache(kickOutSessionCacheName);
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
if (request.getAttribute(getFailureKeyAttribute()) != null) {
return true;
}
return super.onAccessDenied(request, response, mappedValue);
}
/**
* 重写登陆成功后的处理方法,使其跳转到指定的页面,这里是successUrl
*
* @param token 令牌
* @param subject 用户信息
* @param request 请求
* @param response 响应
* @return true 继续过滤,false 跳过之后的过滤;
* @throws Exception 异常
*/
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
Session session = subject.getSession();
String username = (String) subject.getPrincipal();
Serializable sessionId = session.getId();
reentrantLock.lock();
Deque<Serializable> deque = cache.get(username);
if (deque == null) {
deque = new LinkedList<>();
}
if (!deque.contains(sessionId)) {
deque.push(sessionId);
}
while (deque.size() > maxSession) {
Serializable kickOutSessionId;
if (kickOutAfter) {
kickOutSessionId = deque.removeFirst();
} else {
kickOutSessionId = deque.removeLast();
}
try {
Session kickOutSession = sessionManager.getSession(new DefaultSessionKey(kickOutSessionId));
if (kickOutSession != null) {
kickOutSession.setAttribute("kickOut", true);
}
} catch (Exception e) {
}
}
cache.put(username, deque);
reentrantLock.unlock();
WebUtils.getAndClearSavedRequest(request);
WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());
return true;
}
@Override
protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
request.setAttribute(getFailureKeyAttribute(), ae);
}
/**
* 重写该方法,为了将loginType参数保存到token中
*
* @param request 请求
* @param response 响应
* @return
*/
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
String username = getUsername(request);
String password = getPassword(request);
String loginType = getLoginType(request);
return createToken(username, password, request, response, loginType);
}
private AuthenticationToken createToken(String username, String password, ServletRequest request, ServletResponse response, String loginType) {
boolean rememberMe = isRememberMe(request);
String host = getHost(request);
return createToken(username, password, rememberMe, host, loginType);
}
private AuthenticationToken createToken(String username, String password, boolean rememberMe, String host, String loginType) {
return new UsernamePasswordLoginTypeToken(username, password, rememberMe, host, loginType);
}
private String getLoginType(ServletRequest request) {
return WebUtils.getCleanParam(request, getLoginTypeParamName());
}
.....省略getter/setter方法
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
创建MyModularRealmAuthenticator,继承自ModularRealmAuthenticator,重写doMultiRealmAuthentication()方法 /**
* Created by Lancelot on 2017/3/17.
* 重写模块化用户验证器,根据登录界面传递的loginType参数,获取唯一匹配的realm
*/
public class MyModularRealmAuthenticator extends ModularRealmAuthenticator {
private final Logger log = LoggerFactory.getLogger(MyModularRealmAuthenticator.class);
@Override
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) throws AuthenticationException {
Realm uniqueRealm = getUniqueRealm(realms, token);
if (uniqueRealm == null) {
throw new UnsupportedTokenException("没有匹配类型的realm");
}
return uniqueRealm.getAuthenticationInfo(token);
}
/**
* 判断realms是否匹配,并返回唯一的可用的realm,否则返回空
*
* @param realms realm集合
* @param token 登陆信息
* @return 返回唯一的可用的realm
*/
private Realm getUniqueRealm(Collection<Realm> realms, AuthenticationToken token) {
for (Realm realm : realms) {
if (realm.supports(token)) {
return realm;
}
}
log.error("一个可用的realm都没有找到......");
return null;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
创建UserRealm,继承自AuthorizingRealm,并重写里面的三个方法 /**
* Created by Lancelot on 2017/3/17.
* 从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作,可以把 Realm 看成 DataSource,即安全数据源。
* 从数据库中获取认证信息及授权信息
*/
public class UserRealm extends AuthorizingRealm {
/**
* 用户数据DAO
*/
@Autowired
private BiUserMapper userDao;
/**
* 支持的登陆类型
*/
private String supportedLoginType;
/**
* 授权验证
*
* @param principals 认证人
* @return 授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userDao.findRoles(username));
authorizationInfo.setStringPermissions(userDao.findPermissions(username));
return authorizationInfo;
}
/**
* 用户认证
*
* @param token 令牌
* @return 认证信息
* @throws AuthenticationException 认证失败
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
BiUser user = userDao.selectByLoginName(username);
if (user == null) {
throw new UnknownAccountException();
}
if (user.getLockTime() != null && user.getLockTime().after(new Date())) {
throw new LockedAccountException();
}
return new SimpleAuthenticationInfo(
user.getLoginName(),
user.getLoginPwd(),
getName()
);
}
public boolean supports(AuthenticationToken token) {
if (token instanceof UsernamePasswordLoginTypeToken) {
UsernamePasswordLoginTypeToken usernamePasswordLoginTypeToken = (UsernamePasswordLoginTypeToken) token;
return getSupportedLoginType().equals(usernamePasswordLoginTypeToken.getLoginType());
}
return false;
}
public String getSupportedLoginType() {
return supportedLoginType;
}
/**
*spring注入
*/
public void setSupportedLoginType(String supportedLoginType) {
this.supportedLoginType = supportedLoginType;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
创建VerifyCodeRealm,继承自UserRealm,重写里面的两个方法 /**
* Created by Lancelot on 2017/3/16.
* 使用验证码登陆
*/
public class VerifyCodeRealm extends UserRealm {
@Autowired
private BiUserMapper userDao;
@Autowired
private ShiroCacheUtils shiroCacheUtils;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
BiUser user = userDao.selectByLoginName(username);
if (user == null) {
throw new UnknownAccountException();
}
if (user.getLockTime() != null && user.getLockTime().after(new Date())) {
throw new LockedAccountException();
CaptchaVo captcha = shiroCacheUtils.getCaptcha(username);
if (captcha != null && captcha.isExpired()) {
shiroCacheUtils.clearUserCaptcha(username);
throw new CaptchaExpiredException("验证码已过期");
}
if (captcha == null) {
throw new AuthenticationException("尚未发送验证码,请先获取");
}
return new SimpleAuthenticationInfo(
user.getLoginName(),
captcha.getCaptcha(),
getName()
);
}
/**
*重写断言验证码是否一致,为了方便区别错误原因,抛出一个自定义异常
*/
@Override
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
try {
super.assertCredentialsMatch(token, info);
} catch (IncorrectCredentialsException e) {
throw new IncorrectCaptchaException();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
附上spring-shiro的配置文件中需要注意的地方:
<bean id="userRealm" class="com.seawave.shiro.realm.UserRealm">
<property name="authenticationCachingEnabled" value="true"/>
<property name="authenticationCacheName" value="${authenticationCacheName}"/>
<property name="authorizationCachingEnabled" value="true"/>
<property name="authorizationCacheName" value="${authorizationCacheName}"/>
<property name="credentialsMatcher" ref="credentialsMatcher"/>
<property name="cachingEnabled" value="true"/>
<property name="cacheManager" ref="cacheManager"/>
<property name="supportedLoginType" value="usernameAndPassword"/>
</bean>
<bean id="verifyCodeRealm" class="com.seawave.shiro.realm.VerifyCodeRealm">
<property name="authenticationCachingEnabled" value="true"/>
<property name="authenticationCacheName" value="${userAndCaptchaCacheName}"/>
<property name="authorizationCachingEnabled" value="true"/>
<property name="authorizationCacheName" value="${authorizationCacheName}"/>
<property name="credentialsMatcher" ref="simpleCredentialsMatcher"/>
<property name="cachingEnabled" value="true"/>
<property name="cacheManager" ref="cacheManager"/>
<property name="supportedLoginType" value="usernameAndCaptcha"/>
</bean>
<bean id="authenticator" class="com.seawave.shiro.authc.pam.MyModularRealmAuthenticator">
<property name="realms">
<list>
<ref bean="verifyCodeRealm"/>
<ref bean="userRealm"/>
</list>
</property>
</bean>
<bean id="authorizer" class="org.apache.shiro.authz.ModularRealmAuthorizer">
<property name="realms">
<list>
<ref bean="verifyCodeRealm"/>
<ref bean="userRealm"/>
</list>
</property>
</bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="sessionManager" ref="sessionManager"/>
<property name="cacheManager" ref="cacheManager"/>
<property name="authenticator" ref="authenticator"/>
<property name="authorizer" ref="authorizer"/>
</bean>
<bean id="formAuthenticationFilter"
class="com.seawave.shiro.captcha.MyFormAuthenticationFilter">
<property name="usernameParam" value="name"/>
<property name="passwordParam" value="pwd"/>
<property name="loginTypeParamName" value="loginType"/>
<property name="rememberMeParam" value="rememberMe"/>
<property name="successUrl" value="/homePage/gotoHomePage"/>
<property name="sessionManager" ref="sessionManager"/>
<property name="cacheManager" ref="cacheManager"/>
<property name="kickOutSessionCacheName" value="${kickOutSessionCacheName}"/>
<property name="kickOutAfter" value="false"/>
<property name="maxSession" value="1"/>
</bean>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
总结多看源码 耐心细致
|