相关源码:https://gitee.com/virens/multi_spring/tree/master/src/main/java/cn/virens/web/components/shiro
我定义了2个接口类,主要是让Service来实现,为了以后方便 切换Service或者MyBatis
/** 用户登录结果出来,例如保存日志 */
public interface ShiroAuthorizingConsumer {
void onLoginSuccess(String username, String host);
void onLoginFailure(String username, String host);
}
/** 用户登录&鉴权所需的接口 */
public interface ShiroAuthorizingProvider {
String login(String account);
Collection<String> getRoles(String account);
Collection<String> getResources(String account);
}
由于是在Spring框架内使用,并且已经配置好了EhCacheCacheManager
,还有就是为了方便切换缓存管理方案,所以我自己写的Shiro缓存管理,主要参照shiro-ecache包。
SpringCacheManage
实现了org.apache.shiro.cache.CacheManager
接口。
public class SpringCacheManage implements CacheManager, ApplicationContextAware {
private ApplicationContext applicationContext;
private org.springframework.cache.CacheManager cacheManager;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 注入Spring的缓存管理器
* @param cacheManager
*/
public SpringCacheManage(org.springframework.cache.CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
/**
* 获取 缓存
*/
@Override
public <K, V> Cache<K, V> getCache(String name) throws CacheException {
try {
return new SpringCache<K, V>(getCacheManager().getCache(name));
} catch (Throwable t) {
throw new CacheException(t);
}
}
/**
* 获取Spring 缓存管理器,如果为空则直接从Bean里面取
* @return
*/
protected org.springframework.cache.CacheManager getCacheManager() {
if (cacheManager == null) {
this.cacheManager = applicationContext.getBean(org.springframework.cache.CacheManager.class);
}
return cacheManager;
}
}
SpringCache
实现了org.apache.shiro.cache.Cache
接口。
public class SpringCache<K, V> implements Cache<K, V> {
private final org.springframework.cache.Cache cache;
public SpringCache(org.springframework.cache.Cache cache) {
if (cache == null) throw new IllegalArgumentException("Cache argument cannot be null.");
this.cache = cache;
}
@Override
@SuppressWarnings("unchecked")
public V get(K key) throws CacheException {
if (key == null) return null;
try {
ValueWrapper vw = cache.get(key);
if (vw != null) {
return (V) vw.get();
} else {
return null;
}
} catch (Throwable t) {
throw new CacheException(t);
}
}
@Override
public V put(K key, V value) throws CacheException {
if (key == null) return null;
try {
V previous = get(key);
cache.put(key, value);
return previous;
} catch (Throwable t) {
throw new CacheException(t);
}
}
@Override
public V remove(K key) throws CacheException {
if (key == null) return null;
try {
V previous = get(key);
cache.evict(key);
return previous;
} catch (Throwable t) {
throw new CacheException(t);
}
}
@Override
public void clear() throws CacheException {
cache.clear();
}
@Override
public int size() {
throw new UnsupportedOperationException("invoke spring cache abstract size method not supported");
}
@Override
public Set<K> keys() {
throw new UnsupportedOperationException("invoke spring cache abstract keys method not supported");
}
@Override
public Collection<V> values() {
throw new UnsupportedOperationException("invoke spring cache abstract values method not supported");
}
}
SimpleAuthorizingRealm
是继承于AuthorizingRealm
的用户登录/权限校验类,要求上下文中有ShiroAuthorizingProvider
的实现bean,并且bean的名字必须为shiro-simple-interface
,并从中取到实现了ShiroAuthorizingProvider
接口的Bean。
public class SimpleAuthorizingRealm extends AuthorizingRealm implements ShiroRealmInterface {
private Logger logger = LoggerFactory.getLogger(SimpleAuthorizingRealm.class);
@Autowired
@Qualifier("shiro-simple-interface")
private ShiroAuthorizingProvider authorizingProvider;
public SimpleAuthorizingRealm() {
this(new HashedCredentialsMatcher("MD5"));
}
public SimpleAuthorizingRealm(CacheManager manager) {
this(manager, null);
}
public SimpleAuthorizingRealm(CredentialsMatcher matcher) {
this(null, matcher);
}
public SimpleAuthorizingRealm(CacheManager manager, CredentialsMatcher matcher) {
super(manager, matcher);
}
@Override
public void clearAuthorizationInfo(PrincipalCollection principals) {
this.clearCachedAuthorizationInfo(principals);
}
public ShiroAuthorizingProvider getAuthorizingProvider() {
return authorizingProvider;
}
public void setAuthorizingProvider(ShiroAuthorizingProvider authorizingInterface) {
this.authorizingProvider = authorizingInterface;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.debug("执行授权信息...");
try {
String account = (String) getAvailablePrincipal(principals);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addRoles(authorizingProvider.getRoles(account));// 获取用户角色列表
authorizationInfo.addStringPermissions(authorizingProvider.getResources(account)); // 获取用户权限列表
return authorizationInfo;
} catch (Exception e) {
throw new UnauthenticatedException();
}
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
logger.debug("执行身份验证信息...");
// 获取用户信息
String account = String.valueOf(token.getPrincipal());
if (StrUtil.isEmpty(account)) { throw new UnknownAccountException("用户不存在"); }
String password = authorizingProvider.login(account);
if (StrUtil.isEmpty(password)) { throw new UnknownAccountException("用户不存在"); }
return new SimpleAuthenticationInfo(account, password, getName());
}
@Override
protected Object getAuthenticationCacheKey(AuthenticationToken token) {
return token("shrio:authentication:", token);
}
@Override
protected Object getAuthenticationCacheKey(PrincipalCollection principals) {
return object("shrio:authentication:", super.getAvailablePrincipal(principals));
}
@Override
protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
return object("shrio:authorization:", principals);
}
private String token(String pref, AuthenticationToken token) {
return (token == null ? null : (pref + token.getPrincipal()));
}
private String object(String pref, Object obj) {
return (obj == null ? null : (pref + obj.toString()));
}
}
SimpleAuthorizingFilter
是继承于FormAuthenticationFilter
的扩展类,主要是为了添加验证码校验,以及通过ShiroAuthorizingConsumer
接口将登陆结果传递到实现了该接口的Service,以实现日志记录。
public class SimpleAuthorizingFilter extends FormAuthenticationFilter implements ShiroAuthorizingConsumerAware {
private Logger logger = LoggerFactory.getLogger(SimpleAuthorizingFilter.class);
@Autowired
@Qualifier("shiro-simple-interface")
private ShiroAuthorizingConsumer authorizingConsumer;
private boolean useCaptcha = false;// 是否使用验证码
private String captchaParam = "captcha";// 验证码的参数名
/**
* 登录前置处理
*/
@Override
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
logger.debug("onPreHandle");
/** 如果已经登录,并且访问的地址是登录页面,直接重定向到登录成功页面 */
if (isAccessAllowed(request, response, mappedValue)) {
if (isLoginRequest(request, response)) {
WebUtils.issueRedirect(request, response, getSuccessUrl());
return false;
} else {
return true;
}
} else {
return onAccessDenied(request, response, mappedValue);
}
}
/**
* 执行登录操作
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
logger.info("executeLogin,RememberMe:" + isRememberMe(request));
if (isUseCaptcha() && verifyCactcha(request, response) == false) {
return onLoginFailure(null, new CaptchaErrorException(), request, response);
} else {
return super.executeLogin(request, response);
}
}
/**
* 登录成功
*/
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
this.authorizingConsumer.onLoginSuccess(getUsername(request), getHost(request));
return super.onLoginSuccess(token, subject, request, response);
}
/**
* 登录失败
*/
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
this.authorizingConsumer.onLoginFailure(getUsername(request), getHost(request));
return super.onLoginFailure(token, e, request, response);
}
/**
* 登录失败
*/
@Override
protected void setFailureAttribute(ServletRequest request, AuthenticationException e) {
if (e instanceof CaptchaErrorException) {
request.setAttribute("success", false);
request.setAttribute("message", "验证码错误");
request.setAttribute(getFailureKeyAttribute(), e);
} else if (e instanceof UnknownAccountException) {
request.setAttribute("success", false);
request.setAttribute("message", "账号错误");
request.setAttribute(getFailureKeyAttribute(), e);
} else if (e instanceof IncorrectCredentialsException) {
request.setAttribute("success", false);
request.setAttribute("message", "密码错误");
request.setAttribute(getFailureKeyAttribute(), e);
} else {
request.setAttribute("success", false);
request.setAttribute("message", "未知错误");
request.setAttribute(getFailureKeyAttribute(), e);
}
}
/**
* 是否需要验证码
*
* @return
*/
public boolean isUseCaptcha() {
return useCaptcha;
}
/**
* 设置是否需要验证码
*
* @param useCaptcha
*/
public void setUseCaptcha(boolean useCaptcha) {
this.useCaptcha = useCaptcha;
}
/**
* 获取验证码参数字段名
*
* @return
*/
public String getCaptchaParam() {
return captchaParam;
}
/**
* 设置验证码参数字段名
*
* @param captchaParam
*/
public void setCaptchaParam(String captchaParam) {
this.captchaParam = captchaParam;
}
public ShiroAuthorizingConsumer getAuthorizingConsumer() {
return authorizingConsumer;
}
@Override
public void setAuthorizingConsumer(ShiroAuthorizingConsumer authenticationInterface) {
this.authorizingConsumer = authenticationInterface;
}
@Override
protected String getHost(ServletRequest request) {
return RequestUtil.getRemoteAddr((HttpServletRequest) request);
}
/**
* 获取验证码
*
* @param request
* @return
*/
protected String getCaptcha(ServletRequest request) {
return request.getParameter(getCaptchaParam());
}
/**
* 获取缓存在Session里的验证码
*
* @param request
* @param response
* @return
*/
protected String getCaptcha(ServletRequest request, ServletResponse response) {
Subject subject = getSubject(request, response);
if (subject == null) { return null; }
Session session = subject.getSession(false);
if (session == null) { return null; }
return String.valueOf(session.getAttribute(getCaptchaParam()));
}
private boolean verifyCactcha(ServletRequest request, ServletResponse response) {
String tcode1 = getCaptcha(request);
String tcode2 = getCaptcha(request, response);
logger.debug("验证码:{}/{}", tcode1, tcode2);
return StrUtil.equalsIgnoreCase(tcode1, tcode2);
}
}
SpringShiroSourceConfig
是Shiro的配置类。
@Configuration
public class SpringShiroConfig {
@Value("${auth.url.success}")
private String successUrl;
@Value("${auth.url.redirect}")
private String redirectUrl;
@Value("${auth.url.unauthorized}")
private String unauthorizedUrl;
@Value("${auth.api.url.user}")
private String apiUserUrl;
@Value("${auth.api.url.login}")
private String apiLoginUrl;
@Value("${auth.api.url.logout}")
private String apiLogoutUrl;
@Value("${auth.simple.url.user}")
private String simpleUserUrl;
@Value("${auth.simple.url.login}")
private String simpleLoginUrl;
@Value("${auth.simple.url.logout}")
private String simpleLogoutUrl;
@Value("${auth.captcha}")
private Boolean captcha;
@Value("${auth.param.captcha}")
private String captchaName;
@Value("${auth.param.username}")
private String usernameName;
@Value("${auth.param.password}")
private String passwordName;
@Value("${auth.param.rememberme}")
private String rememberMeName;
@Value("${auth.param.failurekey}")
private String failureKeyAttribute;
/**
* Shiro 缓存管理器配置
*
* @param cacheManager
* @return
*/
@Bean("shiro-cachemanager")
public SpringCacheManage springCacheManage(org.springframework.cache.CacheManager cacheManager) {
return new SpringCacheManage(cacheManager);
}
/**
* 会话管理器
*
* @param sessionDAO
* @return
*/
@Bean("shiro-sessionmanager")
public ServletContainerSessionManager sessionManager() {
return new ServletContainerSessionManager();
}
/**
* Realm实现
*
* @param authorizingProvider
* @return
*/
@Bean("shiro-simple-realm")
public SimpleAuthorizingRealm authorizingRealm(@Qualifier("shiro-simple-interface") ShiroAuthorizingProvider authorizingProvider) {
SimpleAuthorizingRealm realm = new SimpleAuthorizingRealm();
realm.setAuthorizationCachingEnabled(true);
realm.setAuthenticationCachingEnabled(true);
realm.setAuthorizingProvider(authorizingProvider);
realm.setAuthorizationCacheName("shiro-authorizationCache");
realm.setAuthenticationCacheName("shiro-authenticationCache");
return realm;
}
/**
* rememberMe管理器
*
* @return
*/
@Bean("shiro-remembermemanager")
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
rememberMeManager.setCookie(new RememberMeCookie());
rememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
return rememberMeManager;
}
/**
* 安全管理器
*
* @return
*/
@Bean("shiro-securitymanager")
public DefaultWebSecurityManager securityManager(//
@Qualifier("shiro-simple-realm") Realm realm, //
@Qualifier("shiro-cachemanager") SpringCacheManage cacheManager, //
@Qualifier("shiro-sessionmanager") SessionManager sessionManager, //
@Qualifier("shiro-remembermemanager") RememberMeManager rememberMeManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
securityManager.setCacheManager(cacheManager);
securityManager.setSessionManager(sessionManager);
securityManager.setRememberMeManager(rememberMeManager);
return securityManager;
}
/**
* Shiro的Web过滤器
*
* @return
* @throws Exception
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("shiro-securitymanager") SecurityManager securityManager) throws Exception {
SimpleShiroFilterFactoryBean factoryBean = new SimpleShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
factoryBean.setUnauthorizedUrl(unauthorizedUrl);
factoryBean.setLoginUrl(simpleLoginUrl);
factoryBean.setSuccessUrl(successUrl);
factoryBean.addFilter("simpleAuthorizingFilter", simpleAuthorizingFilter());
factoryBean.addFilter("simpleLogoutFilter", simpleLogoutFilter());
factoryBean.addFilter("simpleUserFilter", simpleUserFilter());
factoryBean.addFilter("ajaxAuthorizingFilter", ajaxAuthorizingFilter());
factoryBean.addFilter("ajaxLogoutFilter", ajaxLogoutFilter());
factoryBean.addFilter("ajaxUserFilter", ajaxUserFilter());
factoryBean.addFilterChain(simpleLoginUrl, "simpleAuthorizingFilter");
factoryBean.addFilterChain(simpleLogoutUrl, "simpleLogoutFilter");
factoryBean.addFilterChain(simpleUserUrl, "simpleUserFilter");
factoryBean.addFilterChain(apiLoginUrl, "ajaxAuthorizingFilter");
factoryBean.addFilterChain(apiLogoutUrl, "ajaxLogoutFilter");
factoryBean.addFilterChain(apiUserUrl, "ajaxUserFilter");
factoryBean.addFilterChain("/**", "anon");
return factoryBean;
}
/**
* 授权 注解配置
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
/**
* 授权 注解配置
*
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor attributeSourceAdvisor(@Qualifier("shiro-securitymanager") SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor attributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
attributeSourceAdvisor.setSecurityManager(securityManager);
return attributeSourceAdvisor;
}
@Bean
public AjaxUserFilter ajaxUserFilter() {
AjaxUserFilter ajaxUserFilter = new AjaxUserFilter();
ajaxUserFilter.setLoginUrl(apiLoginUrl);
return ajaxUserFilter;
}
@Bean
public AjaxLogoutFilter ajaxLogoutFilter() {
return new AjaxLogoutFilter();
}
@Bean
public AjaxAuthorizingFilter ajaxAuthorizingFilter() {
AjaxAuthorizingFilter ajaxFormFilter = new AjaxAuthorizingFilter();
ajaxFormFilter.setUseCaptcha(captcha);
ajaxFormFilter.setLoginUrl(apiLoginUrl);
ajaxFormFilter.setCaptchaParam(captchaName);
ajaxFormFilter.setUsernameParam(usernameName);
ajaxFormFilter.setPasswordParam(passwordName);
return ajaxFormFilter;
}
@Bean
public SimpleUserFilter simpleUserFilter() {
SimpleUserFilter simpleUserFilter = new SimpleUserFilter();
simpleUserFilter.setLoginUrl(simpleLoginUrl);
return simpleUserFilter;
}
@Bean
public SimpleLogoutFilter simpleLogoutFilter() {
SimpleLogoutFilter simpleLogoutFilter = new SimpleLogoutFilter();
simpleLogoutFilter.setRedirectUrl(redirectUrl);
return simpleLogoutFilter;
}
@Bean
public SimpleAuthorizingFilter simpleAuthorizingFilter() {
SimpleAuthorizingFilter simpleAuthcFilter = new SimpleAuthorizingFilter();
simpleAuthcFilter.setFailureKeyAttribute(failureKeyAttribute);
simpleAuthcFilter.setRememberMeParam(rememberMeName);
simpleAuthcFilter.setPasswordParam(passwordName);
simpleAuthcFilter.setUsernameParam(usernameName);
simpleAuthcFilter.setCaptchaParam(captchaName);
simpleAuthcFilter.setLoginUrl(simpleLoginUrl);
simpleAuthcFilter.setSuccessUrl(successUrl);
simpleAuthcFilter.setUseCaptcha(captcha);
return simpleAuthcFilter;
}
}