写在前面:
最近项目中使用了Shiro,虽然不是自己在负责这一块,但还是抽空学习了下,也可以让自己对shiro有基本的了解。毕竟Shiro安全框架在项目中还是挺常用的。
对于Apache Shiro的基本概念就不在这里一一描述了,资料网上都有,主要还是记录下代码相关的,能够先让自己快速学会使用。
这里的demo(可以测试登录认证,登出,以及授权)主要使用的是ssh框架,所以前提是要将ssh的项目框架搭起来,并导入shiro相关的jar包,然后才是编写配置shiro的相关代码。
1.shiro相关配置文件的编写
web.xml
<!-- shiro 过滤器 start --> <!--spring容器查找名字为shiroFilter(filter-name)的bean并把所有Filter的操作委托给它。--> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <!-- 设置true由servlet容器控制filter的生命周期 --> <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> <!-- shiro 过滤器 end -->
shiro的配置文件applicationContext-shiro.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"> <!--配置自定义Realm,需要继承AuthorizingRealm--> <bean id="myRealm" class="com.myshiro.shiro.MyRealm"> </bean> <!--配置SecurityManager--> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myRealm"/> </bean> <!--实现自己的登出过滤器--> <bean id="signOutFilter" class="com.myshiro.shiro.SignOutFilter"> <!--配置登出重定向的路径--> <property name="redirectUrl" value="/logout.jsp"/> </bean> <!-- 配置 ShiroFilter bean: 该 bean 的 id 必须和 web.xml 文件中配置的 shiro filter 的 name 一致 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <!--配置登录页面,当未认证发送请求时,默认跳转的页面,即登录页面--> <property name="loginUrl" value="/views/login.jsp"/> <!--没有授权默认跳转的页面--> <property name="unauthorizedUrl" value="/noPermission.jsp"/> <!--配置登出过滤器--> <property name="filters"> <map> <entry key="logout" value-ref="signOutFilter"></entry> </map> </property> <!--配置资源过滤器链--> <property name="filterChainDefinitions"> <value> <!--anon表示不拦截,直接放行--> /login = anon <!-- authc 表示需要认证后才能访问,即需要登录后才可以访问,否则跳转到上面配置的登录页面--> /authTest = authc <!-- perms 表示需要有对应的权限才能访问的, 此处perms表示要登录认证成功,并且有对应的pernissionTest权限才可以进行访问 浏览器输入../permissionTest,回车,如果没有通过登录认证,则跳转到上面配置的登录页面;否则去进行授权验证, 如果,授权验证没有通过,则跳转到上面配置的未授权跳转的页面,否则可以访问对应的资源 --> /permissionTest = perms[/permissionTest] <!--当角色是admin的时候才可以访问--> /permissionTest2 = roles[admin] <!--表示在访问url为signOut时,此时会执行登出logout操作, 这里如果不自定义登出过滤器,会默认使用LogoutFilter过滤器来自动完成登出操作,并不需要实现controller层对应的signOut方法 也可以自定义登出过滤器,需要继承LogoutFilter过滤器,这里我自定义了登出过滤器 --> /signOut = logout </value> </property> </bean> </beans>
配置完后,记得使用<import resource="classpath:configs/applicationContext-shiro.xml"/>将其导入进spring配置文件中。
2.自定义实现的Realm
public class MyRealm extends AuthorizingRealm{ @Resource private UserService userService; /** * 授权 * 此方法 只有在shiro的配置文件中配置了权限才会进行调用,例如配置了role,permission * @param pc * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) { //根据自己系统规则的需要编写获取授权信息,这里为了快速入门只获取了用户对应角色的资源url信息 //获取当前登录输入的用户名 String username = (String) pc.fromRealm(getName()).iterator().next(); if (username != null) { User user = null; try { //根据用户名查询对应的user user = userService.getByUserName(username); } catch (Exception e) { e.printStackTrace(); } //根据用户获取对应的role,根据role获取对应的permission Set<Role> roleSet = user.getRoleSet(); List<String> pers = new ArrayList<>(); for(Role r:roleSet){ Set<Permission> permissionSet = r.getPermissionSet(); for(Permission p:permissionSet){ pers.add(p.getUrl()); } } if (pers != null && !pers.isEmpty()) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); for (String each : pers) { //将权限资源添加到用户信息中 会与配置文件中配置的对应权限做对比 info.addStringPermission(each); } return info; } } return null; } /** * 认证,登录验证 * @param at * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken at) throws AuthenticationException { //获取存进shiro token中的用户信息,(token里的信息在Controller层的login方法里存的) UsernamePasswordToken token = (UsernamePasswordToken) at; String username = token.getUsername(); if (username != null && !"".equals(username)) { User user = null; try { //根据用户名去数据库中查询对应的user user = userService.getByUserName(username); if (user != null) { //将数据库中的用户名,密码拿去和存在shiro token中的用户输入的用户名,密码做对比 return new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), getName()); } } catch (Exception e) { e.printStackTrace(); } } return null; } }
3.自定义实现的LogoutFilter
public class SignOutFilter extends LogoutFilter{ @Override protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { Subject subject = getSubject(request,response); String redirectUrl = getRedirectUrl(request,response,subject); try{ subject.logout(); }catch (Exception e){ e.printStackTrace(); } issueRedirect(request,response,redirectUrl); //这里返回false 代表不再去执行controller层的方法, // true代表还会去执行(如果需要在登出的controller里自定义一些东西,例如清除session或者cookie的一些信息, // 这个时候只需要返回true) return true; } }
4.Controller层的相关方法
@Controller("LoginAction") public class LoginAction extends BaseAction{ @Resource private UserService userService; //接收表单填写的用户名 密码 private String userName; private String password; public String login(){ Subject subject = SecurityUtils.getSubject(); //判断当前登录用户是否已经被认证,即是否已经登录了,如果已经登录了,再次发送登录的请求,就不再进行认证了 if(!subject.isAuthenticated()){ //没有登录 进行登录认证 //将登录的用户名,密码存进shiro token中 UsernamePasswordToken token = new UsernamePasswordToken(userName, password); try{ //去自己的MyRealm类中执行doGetAuthenticationInfo()方法,进行登录认证 subject.login(token); } catch ( UnknownAccountException e ) { System.out.println("用户未注册!"); return "error"; } catch ( IncorrectCredentialsException e ) { System.out.println("密码错误!!"); return "error"; } catch ( LockedAccountException e ) { System.out.println("该账户不可用~"); return "error"; } catch ( ExcessiveAttemptsException e ) { System.out.println("尝试次数超限!!"); return "error"; } return "success"; } return "success"; } //认证测试 public void authTest(){ System.out.println("authTest------进来了"); } //权限授权测试 public void permissionTest(){ System.out.println("permissionTest--------进来了"); } //登出测试 public String signOut(){ System.out.println("controller---登出---------"); //实现对session或者其他信息的清除 return "success"; } @Override public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
对应的struts.xml中action访问路径以及对应返回资源视图的配置,以及对应的各种jsp页面这里就不再贴代码了。
测试:
在表单中,填写对应的用户名,密码,然后提交,会去访问/login,此访问地址,在shiro配置中为anon,故直接放行,去controller层的login方法,然后执行到subject.login(token);,会去MyRealm中执行doGetAuthenticationInfo()方法,去验证登录信息,验证后再返回到controller层的login方法中继续执行,根据不同的验证结果,返回对应的结果视图。
登录成功后,在浏览器地址栏输入..../signOut,由于shiro配置文件为logout,则会执行登出操作,这个时候会去自定义SignOutFilter里面执行preHandle()方法,由于此时返回值是true,则还需要去执行controller层的signOut()方法。
登录成功后,在浏览器地址栏输入.../authTest,由于shiro配置中为authc,则需要登录认证成功后才可以继续放行访问,由于已经登录成功,则可以访问成功,会继续执行controller层的authTest()方法;如果还未登录,在浏览器地址栏输入..../authTest,此时跳转到配置的登录页面。
登录成功后,在在浏览器地址栏输入.../permissionTest,由于shiro配置中为perms[/permissionTest],则需要permission权限才可以继续访问,由于已经登录成功,则会去MyRealm中的doGetAuthorizationInfo()方法中进行授权认证,若授权成功,会继续执行controller层的permissionTest()方法;如果还未登录,在浏览器地址栏输入..../permissionTest,此时跳转到配置的登录页面。
对于快速入门,要做到下面三个问题,就已经差不多了
1.什么是shiro?
2.shiro可以用来干嘛?
3.shiro如何使用?
附上两张斌哥ppt中的图,这样会对认证与授权的整个流程有更清楚的认识:
身份认证:
身份授权:
参考资料:
https://blog.csdn.net/u013142781/article/details/50629708?utm_source=blogxgwz0-----Shiro安全框架入门篇(登录验证实例详解与源码)
https://blog.csdn.net/swingpyzf/article/details/46342023/-------Apache Shiro 快速入门教程,shiro 基础教程
https://www.cnblogs.com/Mick-mod/p/8942072.html------ssh框架整合shiro权限
https://blog.csdn.net/shencange/article/details/73289801------使用shiro进行登录密码安全验证
https://blog.csdn.net/chengxuzaza/article/details/72851707------shiro实现系统的退出功能
https://blog.csdn.net/qq_34292044/article/details/79131199------Shiro实现logout操作