因工作需要引入shiro。但系统是基于Servlet3.0 ,而网上关于shiro 的教程大多是spring,因此记录下。
servlet 引入shiro:
1.引入shiro.jar:
shiro-core-1.4.0.jar
shiro-web-1.4.0.jar
shiro-ehcache-1.4.0.jar
shiro+servlet 主要通过配置shiro.ini 进行搭建。
2.shiro.ini
[main]
securityManager=demo.CustomizeWebSecurityManager
[roles]
doctor=*
worker=*
patient=*
[urls]
/** = anon
3.Listener 和filter 直接使用servlet3.0的注解方式引入。注意shirofilter引入对原系统filter链影响。
Listener:
import org.apache.shiro.web.env.WebEnvironment;
/**
*
* @author yuyi
*
*/
@WebListener
public class EnvironmentLoaderListener extends org.apache.shiro.web.env.EnvironmentLoaderListener {
@Override
}
Filter:
@WebFilter(urlPatterns = "/*"})
public class F1ShiroFilter extends org.apache.shiro.web.servlet.ShiroFilter {
}
无状态token验证
参考:
https://www.cnblogs.com/hujunzheng/p/7210157.html
思路是在Filter 里设置Realm验证所需的AuthenticationToken。同时停用shiro默认的servlet - session。参考里思路说得很清楚,就不多对思路累述。
关闭servlet默认的session:
public class CustomizeWebSecurityManager extends org.apache.shiro.web.mgt.DefaultWebSecurityManager {
public CustomizeWebSecurityManager() {
super(new CustomizeRealm());
// 设置authenticator
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
setAuthenticator(authenticator);
// 设置authorizer
ModularRealmAuthorizer authorizer = new ModularRealmAuthorizer();
authorizer.setPermissionResolver(new WildcardPermissionResolver());
setAuthorizer(authorizer);
// 设置取消session
setSessionManager(new DefaultSessionManager() {
{
setSessionValidationSchedulerEnabled(false);
}
});
setSubjectFactory(new StatelessDefaultSubjectFactory());
setSubjectDAO(new DefaultSubjectDAO() {
{
setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator() {
{
setSessionStorageEnabled(false);
}
});
}
});
List<Realm> realms = new ArrayList<>();
realms.add(new CustomizeRealm());
// 设置Realm
setRealms(realms);
SecurityUtils.setSecurityManager(this);
}
}
public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {
@Override
public Subject createSubject(SubjectContext context) {
// 不创建session
context.setSessionCreationEnabled(false);
return super.createSubject(context);
}
}
public class CustomizeFormAuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter {
/**
* 获得登录token
*
* @param request
* @param res
* @return
*/
protected String _GetCookieID(HttpServletRequest request, HttpServletResponse res) {
// 兼容API接口参数传递
String ercloud_id = request.getParameter("token");
if (ercloud_id != null)
return ercloud_id;
Cookie[] cookies = request.getCookies();// 这样便可以获取一个cookie数组
if (cookies == null)
return null;
for (Cookie cookie : cookies) {
if (cookie.getName().equals(CookieEnum.token.toString())) {
ercloud_id = StringUtils.isNotBlank(cookie.getValue()) ? cookie.getValue() : ercloud_id;
}
}
return ercloud_id;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
try {
// 生成无状态Token
// 委托给Realm进行登录
getSubject(request, response).login(new StatelessToken(
_GetCookieID((HttpServletRequest) request, (HttpServletResponse) response), CRealm.doctor));//通过realm name 提交给对应的realm进行验证。
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
public class ModularRealmAuthenticator extends org.apache.shiro.authc.pam.ModularRealmAuthenticator {
/**
* 设置验证策略
*/
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
throws AuthenticationException {
// 判断getRealms()是否返回为空
assertRealmsConfigured();
StatelessToken statelessToken = (StatelessToken) authenticationToken;
Collection<Realm> realms = getRealms();
// 登录类型对应的所有Realm
Collection<Realm> typeRealms = new ArrayList<>();
for (Realm realm : realms) {
if (realm.getName().contains(statelessToken.getRealm().toString()))
typeRealms.add(realm);
}
if (typeRealms.size() == 1) {
return doSingleRealmAuthentication(typeRealms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(typeRealms, authenticationToken);
}
}
}
上面的重点是在于filter的整合。最简单的是直接在shiro.ini中进行配置:
[main]
#定义一个filter
filterdoctor= demo.CustomizeFormAuthenticationFilter
[urls]
#authc
/** = filterdoctor, roles[doctor]
如此非servlet-session的shiro 验证就可以了。
shiro动态请求配置。
参考:
https://www.w3cschool.cn/shiro/h5it1if8.html
实现
数据库:
修改shiro listener :
@WebListener
public class EnvironmentLoaderListener extends org.apache.shiro.web.env.EnvironmentLoaderListener {
@Override
public WebEnvironment initEnvironment(ServletContext servletContext) throws IllegalStateException {
servletContext.setInitParameter("shiroEnvironmentClass", "demo.IniWebEnvironment");//使用自己的shiro.ini 解析类
return super.initEnvironment(servletContext);
}
}
IniWebEnvironment
public class IniWebEnvironment extends org.apache.shiro.web.env.IniWebEnvironment {
private final static Logger log = LoggerFactory.getLogger(IniWebEnvironment.class);
public static IniWebEnvironment iniWebEnvironment;
@Override
public void init() {
super.init();
iniWebEnvironment = this;
iniWebEnvironment.refash();
}
public void refash() {
PathMatchingFilterChainResolver p = (PathMatchingFilterChainResolver) getFilterChainResolver();
DefaultFilterChainManager f = (DefaultFilterChainManager) p.getFilterChainManager();
f.getFilters().clear();//清楚filter
f.getChainNames().clear();//清楚url与filter之间的映射
RefashFilterChain refashFilterChain = new RefashFilterChain();
refashFilterChain.filters.forEach((k, v) -> {
f.addFilter(k, v);
});
refashFilterChain.chains.forEach((k, v) -> {
f.createChain(k, v);
});
log.info(" FilterChain refash OK");
}
class RefashFilterChain {
@SuppressWarnings("serial")
RefashFilterChain() {
List<Map<String, Object>> list = QueryHelper.query("select * from urlchainmanage");
if (list.isEmpty()) {
list.add(new HashMap<String, Object>() {
{
put("chain", "/*");
put("chaindefinition", "anon");
put("filterclz", "org.apache.shiro.web.filter.authc.AnonymousFilter");
}
});
}
list.forEach(a -> {
if (a.containsKey("chain") && StringUtils.isNotBlank((String) a.get("chain")))
chains.put(StringUtils.trim((String) a.get("chain")),
StringUtils.trim((String) a.get("chaindefinition")));
if (a.containsKey("filterclz") && StringUtils.isNotBlank((String) a.get("filterclz"))) {
List<String> filterclz = Arrays.asList(((String) a.get("filterclz")).split(","));
String[] chaindefinitions = ((String) a.get("chaindefinition")).split(",");
String chaindefinition;
for (int i = 0; i < filterclz.size(); i++) {
if (StringUtils.isNotBlank(filterclz.get(i))) {
if (chaindefinitions.length <= i) {
log.error(" 该数据错误:{}", a);
return;
}
int j = -1;
chaindefinition = chaindefinitions[i];
if ((j = chaindefinition.indexOf("[")) != -1)
chaindefinition = chaindefinition.substring(0,
(j != -1 ? j : chaindefinition.length()));
try {
filters.put(StringUtils.trim(chaindefinition), (Filter) ClassLoaderUtil
.loadClass(StringUtils.trim(filterclz.get(i))).newInstance());
} catch (InstantiationException | IllegalAccessException e) {
log.error(" filter load error :{} ", filterclz.get(i), e);
}
}
}
}
});
}
public Map<String, Filter> filters = new HashMap<String, Filter>();
public Map<String, String> chains = new HashMap<String, String>();
}
}
思路:
org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver implements FilterChainResolver 为shiro的url映射filter方法接口,那么修改这个接口返回的filter就能实现自定义设置请求配置。
通过查看源码,PathMatchingFilterChainResolver.getFilterChainManager() = org.apache.shiro.web.filter.mgt.DefaultFilterChainManager 中实际存放了filter和请求映射集
public class DefaultFilterChainManager implements FilterChainManager {
private Map<String, Filter> filters; //pool of filters available for creating chains
private Map<String, NamedFilterList> filterChains; //key: chain name, value: chain
}
在org.apache.shiro.web.env.EnvironmentLoaderListener extends org.apache.shiro.web.env. EnvironmentLoader 中,指明可以设置继承于WebEnvironment的shiro初始类。
/**
* Return the WebEnvironment implementation class to use, based on the order of:
* <ul>
* <li>A custom WebEnvironment class - specified in the {@code servletContext} {@link #ENVIRONMENT_ATTRIBUTE_KEY} property</li>
* <li>{@code ServiceLoader.load(WebEnvironment.class)} - (if more then one instance is found a {@link ConfigurationException} will be thrown</li>
* <li>A call to {@link #getDefaultWebEnvironmentClass()} (default: {@link IniWebEnvironment})</li>
* </ul>
*
* @param servletContext current servlet context
* @return the WebEnvironment implementation class to use
* @see #ENVIRONMENT_CLASS_PARAM
* @param servletContext the {@code servletContext} to query the {@code ENVIRONMENT_ATTRIBUTE_KEY} property from
* @return the {@code WebEnvironment} to be used
*/
protected WebEnvironment determineWebEnvironment(ServletContext servletContext) {
Class<? extends WebEnvironment> webEnvironmentClass = webEnvironmentClassFromServletContext(servletContext);
WebEnvironment webEnvironment = null;
// try service loader next
if (webEnvironmentClass == null) {
webEnvironment = webEnvironmentFromServiceLoader();
}
// if webEnvironment is not set, and ENVIRONMENT_CLASS_PARAM prop was not set, use the default
if (webEnvironmentClass == null && webEnvironment == null) {
webEnvironmentClass = getDefaultWebEnvironmentClass();
}
// at this point, we anything is set for the webEnvironmentClass, load it.
if (webEnvironmentClass != null) {
webEnvironment = (WebEnvironment) ClassUtils.newInstance(webEnvironmentClass);
}
return webEnvironment;
}
/**
* Servlet Context config param for specifying the {@link WebEnvironment} implementation class to use:
* {@code shiroEnvironmentClass}
*/
public static final String ENVIRONMENT_CLASS_PARAM = "shiroEnvironmentClass";
private Class<? extends WebEnvironment> webEnvironmentClassFromServletContext(ServletContext servletContext) {
Class<? extends WebEnvironment> webEnvironmentClass = null;
String className = servletContext.getInitParameter(ENVIRONMENT_CLASS_PARAM);
...
}
在默认的org.apache.shiro.web.env.IniWebEnvironment 类中提供有返回当前shiro PathMatchingFilterChainResolver 对象的方法:DefaultWebEnvironment.getFilterChainResolver();
至此,所有问题解决。