我在shiro学习上花费了一些时间,shiro的资料网上一大推,以前本身学习的知识点一直记录在有道云笔记上,有道云有本身的好处 那就是没有网络的时候依然能够记录一些东西,可是弊端就是不能与你们一块儿分享和讨论,最后仍是选择在segmentFault,一上来发现本身声望值是负数,有点小悲伤啊,之后学习到东西后本身会在这里写写记记,一是概括梳理知识且本身记性很差,方便之后自我回忆,二是但愿能和你们讨论,我有不对的地方但愿能有大神指点。废话很少说,写一下我最近学习的shiro的用法。java
大致的登陆认证流程如上图,当进入登陆页面后,会先进入经过shiroFilter安全认证过滤器,而后读取数据库信息来进行登陆和受权的认证,这一部分是交给realm来进行的,当认证成功后会跳转到successURL对应的地址中去若是失败会跳转到loginUrl中。图上的的${adminPath}只是从配置文件properties中读出配置参数罢了,能够把它当作/a。web
<!-- Apache Shiro --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
web.xml基本上是固定模板,/*拦截了全部的请求,可是记住下shiroFilter这个名字。spring
<description>Shiro Configuration</description> <!-- 加载配置属性文件 --> <context:property-placeholder ignore-unresolvable="true" location="classpath:jeesite.properties" /> <!-- (会被引入)Shiro权限过滤过滤器定义 --> <bean name="shiroFilterChainDefinitions" class="java.lang.String"> <constructor-arg> <value> /static/** = anon /userfiles/** = anon ${adminPath}/login = authc ${adminPath}/logout = logout ${adminPath}/sys/** = roles[sys] ${adminPath}/cms/** = perms[cms:view] ${adminPath}/** = user </value> </constructor-arg> </bean> <!-- (扩展)安全认证过滤器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="${adminPath}/login" /> <property name="successUrl" value="${adminPath}/success" /> <property name="unauthorizedUrl" value="/unauthorized.jsp" /> <property name="filters"> <map> <entry key="authc" value-ref="formAuthenticationFilter"/> </map> </property> <property name="filterChainDefinitions"> <ref bean="shiroFilterChainDefinitions"/> </property> </bean> <!-- (扩展)定义Shiro安全管理配置 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- 经过realm来读取身份信息和受权信息 --> <property name="realm" ref="systemAuthorizingRealm" /> </bean> <!-- (固定)保证明现了Shiro内部lifecycle函数的bean执行 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!--(固定) AOP式方法级权限检查 --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"> <property name="proxyTargetClass" value="true" /> </bean> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean>
XML配置文件中不少也是固定不变的,官网文档也有介绍,anon表示请求对应的路径不认证,authc表示请求对应的路径须要登陆认证,roles[sys]表示请求对应的路径须要角色为sys的才容许,perms[cms:view]表示请求对应的路径须要有cms:view权限的才容许。这里的安全认证过滤器的名字要与web.xml中的shiroFilter同样。数据库
<property name="loginUrl" value="${adminPath}/login" />
表示登陆失败后会跳转到对应请求apache
<property name="successUrl" value="${adminPath}/success" />
表示登陆成功后会跳转的请求安全
<property name="unauthorizedUrl" value="/unauthorized.jsp" />
表示访问了无权访问的连接后跳转的请求。网络
<entry key="authc" value-ref="formAuthenticationFilter"/>
表示 authc这个会经过FormAuthenticationFilter这个类来验证app
<property name="realm" ref="systemAuthorizingRealm" />
这是咱们自定义的realm来认证登陆和受权信息的jsp
实验的目录结构,如上图ide
@Service public class FormAuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter{ public static final String DEFAULT_CAPTCHA_PARAM = "validateCode"; private String captchaParam = DEFAULT_CAPTCHA_PARAM; @Override protected AuthenticationToken createToken(ServletRequest request,ServletResponse response) { System.out.println("-------------进入建立token方法-------------"); // TODO Auto-generated method stub String username = getUsername(request); String password = getPassword(request); if(password == null){ password = ""; } String captcha = getCapcha(request); System.out.println("-------------出建立token方法-------------"); return new UsernamePasswordToken(username,password,captcha); } public String getCapcha(ServletRequest request){ return WebUtils.getCleanParam(request, captchaParam); } public String getSuccessUrl(){ return super.getSuccessUrl(); } @Override protected void issueSuccessRedirect(ServletRequest request,ServletResponse response) throws Exception { // TODO Auto-generated method stub、 System.out.println("-------------issueSuccessRedirect-------------"); WebUtils.issueRedirect(request, response, getSuccessUrl()); } @Override protected boolean onLoginFailure(AuthenticationToken token,AuthenticationException e, ServletRequest request, ServletResponse response) { // TODO Auto-generated method stub System.out.println("-------------onLoginFailure-------------"); String className = e.getClass().getName(); String message = ""; System.out.println("=========e.getCLass().getName()===========:"+className); if(IncorrectCredentialsException.class.getName().equals(className) || UnknownAccountException.class.getName().equals(className)){ message = "用户名或密码错误"; }else{ message = "系统出现点问题,请稍后再试!"; e.printStackTrace(); // 输出到控制台 } request.setAttribute("message",message); return true; } }
当咱们输入完用户密码后须要进入一个 shiroFilter
安全认证过滤器,这里为何须要重写了呢,其实本质是shiro已经给咱们提供了相应的 FormAuthenticationFilter
类,能够在其中根据用户输入的信息建立 token 来供之后流程的认证,可是若是咱们还想加入一些其余的东西来一块儿建立这个 token,好比说咱们经过用户名、密码、验证码、是否记住我、是不是手机端登陆等等信息来一块儿组成一个token的时候,这个时候咱们就能够重写 FormAuthenticationFilter
中的一些方法来实现了。
WebUtils.getCleanParam(request, captchaParam)
是 shiro 给咱们提供的封装,其实就是 request.getParameter(paramName)
的一个封装罢了,咱们能够经过它来获取前台输入的参数。
public class UsernamePasswordToken extends org.apache.shiro.authc.UsernamePasswordToken{ private static final long serialVersionUID = 1L; private String captcha; public UsernamePasswordToken(String username,String password){ super(username,password); } public UsernamePasswordToken() { super(); } public UsernamePasswordToken(String username,String password,String captcha){ super(username,password); this.captcha = captcha; } public String getCaptcha() { return captcha; } public void setCaptcha(String captcha) { this.captcha = captcha; } }
经过重写UsernamePasswordToken,目的是来根据开发者想建立token的参数来构造出一个新的UsernamePasswordToken以供使用。我这里为了简化只写了用户名密码参数。
此时此刻咱们就有了token,下面开始咱们的认证啦~
@Service public class SystemAuthorizingRealm extends AuthorizingRealm{ @Autowired private SystemService systemService; /** * 登陆认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { // TODO Auto-generated method stub System.out.println("-------------进入登陆认证方法-------------"); UsernamePasswordToken token = (UsernamePasswordToken) authcToken; String username = token.getUsername(); User user = systemService.getUserByName(username); System.out.println("-------1----getName()--------:"+getName()); if(user != null){ System.out.println("-------2----getName()--------:"+getName()); System.out.println("-------------登陆认证结束--返回SimpleAuthenticationInfo-------------"); return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName()); }else{ System.out.println("------------登陆认证结束--返回null------------"); return null; } } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // TODO Auto-generated method stub System.out.println("-------------进入受权认证方法-------------"); // Principal principal = (Principal) getAvailablePrincipal(principals); String name = (String) principals.getPrimaryPrincipal(); // User user = systemService.getUserByName(principal.getName()); User user = systemService.getUserByName(name); if(user != null){ SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); List<Role> Roles = systemService.getRoleByUserId(user.getId()); for(Role r:Roles){ System.out.println("role---> "+r.getRole()); info.addRole(r.getRole()); List<Perm> Perms = systemService.getPermByRoleId(r.getId()); if(Perms != null && Perms.size() >0 ){ for(Perm p:Perms){ System.out.println("perm---> "+p.getPermission()); info.addStringPermission(p.getPermission()); } } } System.out.println("-----------受权认证结束--返回info------------"); return info; }else{ System.out.println("-----------受权认证结束--返回null------------"); return null; } } }
在 realm 中咱们须要继承 AuthorizingRealm,并重写两个方法:
doGetAuthenticationInfo 方法:用户身份认证方法,根据参数返回
SimpleAuthenticationInfo,若是登陆成功则跳转到xml配置文件中的 successUrl 地址,若是登陆失败则跳转到 loginUrl 地址;
doGetAuthorizationInfo 方法:身份权限认证,利用了 SimpleAuthorizationInfo,把角色和权限都给予 SimpleAuthorizationInfo 来进行受权;在是 shiro 中若是访问了无权访问的地址,则会跳转到 xml 配置文件中
unauthorizedUrl 对应的地址。
到这里基本上shiro就完毕了,当咱们登陆时候的顺序:
上图为登陆成功的流程
上图为登陆失败的流程
这时候 shiro 已经记住了用户的信息,当再请求路径的时候就不会继续验证了,在此输入请求拦链接的时候就会直接去找对应的 action 而不会再进入安全过滤器,全部咱们想在此登陆须要退出当前用户才行。以下代码能够退出当前用户,这样咱们再请求登陆的时候就会继续安全认证了。
SecurityUtils.getSubject().logout();
当咱们登陆成功后,咱们用是sys角色可是没有 perms[cms:view]
权限 的用户访问 a/sys 是能够请求成功,可是访问 a/cms 是会跳转到 shiro 的 xml 配置文件的unauthorizedUrl 对应的地址,表示权限不足不可访问。
上图为受权成功,用户有该请求权限,跳转到对应 action 中
上图为受权失败,跳转到 unauthorized.jsp 页面,提示无权访问
之后会有新的内容会加已补充,好比shiro的标签和shiro和ehcache的整合等等。