笔记:springboot、springmvc全注解入门整合shiro
功能介绍
2015-11-19注 :最近看了架构探险中对shiro的讲解,下面这篇文章,在使用的方式上是正确的,有一些配置和使用至于是为什么,则需要你们自己去看架构探险章节的说明了,时隔一年来看这篇文章还是觉得很有价值,在当时也只会用使用能满足自己的要求。
本文的认证方法处理有误,正确的应该是:login提交用户表单提交的用户名和密码,doGetAuthenticationInfo认证方法里面放置正确的用户名和密码,让框架去匹配是否通过认证,以抛出异常的方式进行通知调用处没有通过认证,session中不会存放该用户的信息。
该示例:大概功能如下:
由于角色可以自由创建,拥有权限分配的功能的帐号,可以把自身的权限分配给创建的角色。所以本人只想到了,使用请求地址来控制每一个权限。
整合开始:
1.添加shiroFilter过滤器
在能替代web.xml的配置类中添加过滤器
/**
* 用注解方式,类里面配置,完全取代 web.xml中的配置
* @author admin
*/
@Order(1) //第一启动顺序
@HandlesTypes(WebApplicationInitializer.class) //可能是标识这个是一个web上下文初始化的类(在springboot中该类需要在WebMvcConfigurerAdapter(可自定义继承该类)中加载调用)
public class WebXml implements ServletContextInitializer {
Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
logger.info("加载shiro过滤器,与spring集成");
/**
* 找名字为shiroFilter(filter-name)的bean 并把所有Filter的操作委托给它。然后将ShiroFilter
* 配置到spring容器即可
*/
Dynamic shiroFilter = servletContext.addFilter("shiroFilter", DelegatingFilterProxy.class);
shiroFilter.setInitParameter("argetFilterLifecycl", "true");
shiroFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
shiroFilter.setAsyncSupported(true);
}
}
2.配置WebMvcConfigurer
下面的类中有点多,但是指需要添加与shiro先关的几个bean就成了。
之前的是在项目中配置好的
public class WebConfig extends WebMvcConfigurerAdapter {
Logger logger = LoggerFactory.getLogger(getClass());
@Bean
public ServletContextInitializer getMyFilter() {
logger.info("注册web.xml中对应的配置都可以在这里类里面编写-----------");
return new WebXml();
}
/**
* 添加全局异常处理器 1:这里好像是不用屏蔽 springmvc自动注册的全局异常处理器 2:编写自定义处理器 实现
* HandlerExceptionResolver 3:在这里加载
* */
@Bean
public HandlerExceptionResolver getHandlerExceptionResolver() {
logger.info("加载自定义异常处理器");
return new ExceptionResolverCustom();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
logger.info("自定义拦截器在这里增加");
registry.addInterceptor(new LoginInterceptor());
super.addInterceptors(registry);
}
/*
* @Bean public InternalResourceViewResolver
* getInternalResourceViewResolver() { logger.info("加载视图解析器,配置springmvc的视图解析器,一般用于jsp开发?");
* InternalResourceViewResolver resolver = new
* InternalResourceViewResolver(); resolver.setSuffix(".html"); return
* resolver; }
*/
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
logger.info("加载错误页面相关处理,spring boot中可以对根据对应的状态码,比如404,等添加指定的页面");
return new MyCustomizer();
}
//会话ID生成器
@Bean
public SessionIdGenerator sessionIdGenerator() {
JavaUuidSessionIdGenerator jusg = new JavaUuidSessionIdGenerator();
return jusg;
}
// <!-- 会话DAO,用来方便的获得 当前用户的session,也就是说,在任何一个普通类里面都可以使用Subject subject = SecurityUtils.getSubject(); Session session = subject.getSession(); 这个代码来获取当前用户的session
-->
@Bean
public EnterpriseCacheSessionDAO sessionDao() {
EnterpriseCacheSessionDAO ecd = new EnterpriseCacheSessionDAO();
ecd.setSessionIdGenerator(this.sessionIdGenerator());
ecd.setCacheManager(this.shiroCacheManager());
return ecd;
}
private EhCacheManager ec = null;
//缓存,这个缓存,没有集成成功。
@Bean
public EhCacheManager shiroCacheManager() {
if(ec == null) {
ec = new EhCacheManager();
ec.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
}
return ec;
}
//会话管理器
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager dwsm = new DefaultWebSessionManager();
dwsm.setGlobalSessionTimeout(1800000); // 超时
dwsm.setDeleteInvalidSessions(true);
// dwsm.setSessionValidationSchedulerEnabled(true);
// dwsm.setSessionValidationScheduler(this.);
// dwsm.setCacheManager(this.shiroCacheManager());
dwsm.setSessionDAO(this.sessionDao());
return dwsm;
}
//安全管理器
@Bean
public SecurityManager securityManager() {
UserRealm singleRealm = new UserRealm();
singleRealm.setAuthorizationCachingEnabled(true);
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager(singleRealm); //加载自定义realm域
dwsm.setSessionManager(this.sessionManager());
return dwsm;
}
//<!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
@Bean
public MethodInvokingFactoryBean methodInvokingFactoryBean() {
MethodInvokingFactoryBean mifb = new MethodInvokingFactoryBean();
mifb.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
mifb.setArguments(new Object[] {this.securityManager()});
return mifb;
}
//web过滤器
@Bean(name="shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean () {
ShiroFilterFactoryBean sffb = new ShiroFilterFactoryBean();
sffb.setSecurityManager(this.securityManager());
sffb.setLoginUrl("/login.html");//设置登录页面
sffb.setUnauthorizedUrl("/unAuthc.html"); //未授权跳转的页面
Map<String, Filter> filters = new HashMap<>();
filters.put("sysUser", new SysUserFilter()); //自定义过滤器,我是用它来拦截自定义的权限监测调用
sffb.setFilters(filters);
//设置对应的url对应的过滤链
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
filterChainDefinitionMap.put("/login.html", "anon");
filterChainDefinitionMap.put("/system/login/login", "anon");
filterChainDefinitionMap.put("/system/login/logOut", "anon");
filterChainDefinitionMap.put("/errorCustom.html", "anon");
filterChainDefinitionMap.put("/unAuthc.html", "anon");
filterChainDefinitionMap.put("/views/app.js", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/imag/**", "anon");
filterChainDefinitionMap.put("/wxMessagePic/*", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/**", "sysUser");
//可以看到对于一些公共的地址我进行了放行,对于/**使用了自定义的过滤器进行处理,意思就是说:除了以上放行的地址,其他的地址都会被调用自定义的过滤器方法中
sffb.setFilterChainDefinitionMap(filterChainDefinitionMap);
return sffb;
}
//shiro 生命周期处理器
@Bean
public LifecycleBeanPostProcessor LifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
3.编写自定义UserRealm
import org.apache.shiro.realm.AuthorizingRealm;
public class UserRealm extends AuthorizingRealm {
/**
* 获取授权信息,用户所拥有的角色和权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//这里能得到这个参数,就是在下面的doGetAuthenticationInfo返回的用户验证信息。(这个是单realm的实现,直接调用这个方法,就能获得自定义的对象了)
ShiroPrincipal shiroPrincipal = (ShiroPrincipal) principals.getPrimaryPrincipal();
Set<String> psIds = null;
if (shiroPrincipal.isAuthorized()) {
psIds = shiroPrincipal.getAuthorities();
} else {
ISecurityService iss = Config.webApplicationContext.getBean(ISecurityService.class);
try {
psIds = iss.findByUsername(shiroPrincipal.getUsername());
shiroPrincipal.setAuthorized(true);
} catch (Exception e) {
e.printStackTrace();
}
}
SimpleAuthorizationInfo si = new SimpleAuthorizationInfo();
si.setStringPermissions(psIds); // setRoles 暂时只实现权限判断
return si;
}
/**
* 获取身份验证相关信
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//这个token,是在登录方法里面,调用login方法,把用户名和密码传递过来的
//Subject subject = SecurityUtils.getSubject();
//subject.login(new UsernamePasswordToken(systemVo.getUsername(), systemVo.getPassword()));
String username = (String) token.getPrincipal(); // 得到用户名
String password = new String((char[]) token.getCredentials()); // 得到密码
// 如果身份认证验证成功,返回一个AuthenticationInfo实现
// 这里处理的流程:暂时是,先通过了系统的自定义流程之后,再调用shiro的login功能,跳转到这里,所以
// 就不用去获取用户等数据了。直接返回对象:简单的对象实体 new SimpleAuthenticationInfo(username,
// password, getName());
// 第一个参数,可以在授权方法中获取到
ShiroPrincipal principal = new ShiroPrincipal(username);
return new SimpleAuthenticationInfo(principal, password, this.getName());
}
}
4.编写自定义过滤器
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.PathMatchingFilter;
public class SysUserFilter extends PathMatchingFilter {
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
String url = req.getRequestURI();// 用户请求的地址
Subject subject = SecurityUtils.getSubject();
String username = (String)subject.getPrincipal();
if("superadmin".equals(username)) { //系统超级管理员放行
return true;
}
Integer rulKey = this.matchingURL(url);
if(subject.isPermitted(rulKey+"")) { //如果有权限。则放行
return true;
}else {
String method = req.getMethod();
String header = req.getHeader("Accept");
if(header.startsWith("application/json")) {
JSONObject responseJSONObject = new JSONObject();
responseJSONObject.put("0", "你没有该权限");
res.setContentType("application/json; charset=utf-8");
res.setCharacterEncoding("UTF-8");
PrintWriter out = null;
out = res.getWriter();
out.append(responseJSONObject.toString());
}
}
res.sendRedirect("/unAuthc.html");
return false;
}
/**
*
* @Title: matchingURL
* @Description:根据当前请求地址获取标准的公共权限地址,只要是正常的访问。就应该能和公共权限中 一一对应查找到
* @param currentUrl
* @return Integer 获取到后,返回该公共地址的权限id,返回null,则表示请求的资源在公共权限库中没有找到(待定义处理)
* @version V0.1
* @createDate 2015年3月18日 下午4:00:40
* @updateDate 2015年3月18日 下午4:00:40
*/
private Integer matchingURL(String currentUrl) {
Map<Integer, String> authUrls = StaticCache.authUrls;
for (Map.Entry<Integer, String> url : authUrls.entrySet()) {
if(currentUrl.contains(url.getValue())) { //匹配上
return url.getKey();
}
}
return null;
}
}
5.总结
用一张图来表示,shiro大概的入门调用流程,这个是自己花了一天时间理解的。也不知道对不对。反正就先这样用着先。
总结步骤:
1.添加shiro过滤器入口(一定要保证白过滤器在其他过滤器之前,至少在配置清单中在最前面)
2:配置安全管理器,设置自定义realm
3:配置web过滤器(也就是定义shiroFilter,定义的入口会搜寻该对象),设置权限,设置自定义过滤器拦截权限处理