Shiro
Apache Shiro 是一个强大易用的JAVA安全框架,提供了认证、授权、加密、会话管理等功能
- 认证-用户身份识别,等同于用户登录,判断用户是否登录,如果当前用户未登录则会拦截当前请求。
- 授权-访问控制,当用户登录后,判断其角色角色身份是否有相应的权限访问相应的资源,如果没有当前权限则则拦截请求。
- 密码加密-保护或隐藏数据被窥视;例如密码的明文加密后只能看到密文,保护账户信息,shrio提供的密码加密对MD5进行了二次封装,不可逆让密码保护更加安全。
- 会话管理-Shiro 根据传统的Session抽象了一个自己的 Session来管理主体与应用之间交互的数据,控制Session的生命周期。
从工作流程来看shrio(Shiro 不提供维护用户 / 权限,而是通过 Realm 让开发人员自己注入)
- Subject:主体,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等,即一个抽象概念。所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager,可以把 Subject 认为是一个门面,SecurityManager 才是实际的执行者。
- SecurityManager:安全管理器,即所有与安全有关的操作都会与 SecurityManager 交互,且它管理着所有 Subject,可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器。
- Realm:域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法。也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作,可以把 Realm 看成 DataSource,即安全数据源。
所以,Shiro的工作流程是:应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager,
我们给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
Shrio通过一些列过滤器来对认证和授权进行控制
Shrio的过滤器:
以上的过滤器分为两类:
一、认证的过滤器:anno(不认证也可以访问)、authcBasic,authc(必须认证才能访问),user
二、授权的过滤器:perms(指定资源需要哪些权限才能被访问),roles,ssl,rest,port
例子:在Spring配置文件中通过配置ShrioFilter的filterChainDefinitions过滤链对不同的Url分配不同的过滤方式,详细看配置文件部分
-------------------------------------------------------------------------------------------------------------------------------------------
通过实例来使用Shrio安全验证框架(web应用spring与shrio的集成:登录访问页面实例)
maven依赖:见项目源码 其他配置文件参考源码(源码地址在文章底部)
tomcat的配置文件web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>WebShiro</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext-*.xml</param-value> </context-param> <!-- Spring监听器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 处理POST提交乱码问题 --> <filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encoding</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping> <!-- 前端控制器 --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 默认找 /WEB-INF/[servlet的名称]-servlet.xml --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/springmvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <!-- 1. /* 拦截所有 jsp js png .css 真的全拦截 建议不使用 2. *.action *.do 拦截以do action 结尾的请求 肯定能使用 ERP 3. / 拦截所有 (不包括jsp) (包含.js .png.css) 强烈建议使用 前台 面向消费者 www.jd.com/search /对静态资源放行 --> <url-pattern>*.do</url-pattern> </servlet-mapping> <!-- shiro过滤器,DelegatingFilterProx会从spring容器中找shiroFilter --> <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-app>
shrio配置文件:applicationContext-shrio.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.xsd"> <!--Shrio过滤器工厂Bean 通过属性注入的方式注入--> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" depends-on="myAuthorizationFilter"> <!--安全管理器--> <property name="securityManager" ref="securityManager"/> <!--认证相关配置:当用户没有登陆访问资源就会跳转到此页面--> <property name="loginUrl" value="/login.html"/> <!--授权相关配置:当用户没有访问权限的资源就跳转到次页面--> <property name="unauthorizedUrl" value="/error.html"/> <property name="filters"> <map> <entry key="perms" value-ref="myAuthorizationFilter"></entry> </map> </property> <!--过滤链:针对不同url指定不同的过滤器--> <!--配置login.do、error.html为不需要认证的URL,配置哪些资源需要哪些权限才能访问 anon为不需要认证和授权都可以访问 perms为需要什么权限才能访问资源 authc为需要认证后才能访问资源 注意:一定要按照以上的顺序写配置,不然权限拦截会失败 --> <property name="filterChainDefinitions"> <value> /error.html = anon /login.html = anon /login.do = anon /custom.html = perms["用户"] /manager.html = perms["管理员","超级管理员"] /*.do = authc /*.html = authc </value> </property> </bean> <!--用户自定义的Realm--> <bean id="userRealm" class="work.yanghao.realm.UserRealm"> <property name="userService" ref="userService"></property> </bean> <!--自定义授权过滤器--> <bean id="myAuthorizationFilter" class="work.yanghao.filter.MyAuthorizationFilter"></bean> <!--安全管理器 Shrio核心组件:所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理--> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="userRealm"></property> </bean> </beans>
实现Shrio的认证
我们知道Shrio通过Subject----->SecurityManager----->Realm来执行认证操作的,我们要自定义Realm域,提供安全的数据域对象。来看看用户认证(即登录)是如何认证的。
-----当用户没有被认证,请求都会被拦截(除了/error.html和/login.do)转到页面login.html,定义配置login.do不需认证是因为我们发起登录请求login.do的时候不想被Shrio拦截。
<!--配置login.do、error.html为不需要认证的URL,配置哪些资源需要哪些权限才能访问 anon为不需要认证和授权都可以访问 perms为需要什么权限才能访问资源 authc为需要认证后才能访问资源 注意:一定要按照以上的顺序写配置,不然权限拦截会失败 --> <property name="filterChainDefinitions"> <value> /error.html = anon /login.html = anon /login.do = anon /html/custom.html = perms["用户"] /html/manager.html = perms["管理员","超级管理员"] /*.do = authc /*.html = authc /* = authc </value> </property>
-----Controller层代码:
@Controller public class UserController { @RequestMapping("login") public String login(String username,String password){ //创建令牌 UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password); //得到subject Subject subject = SecurityUtils.getSubject(); //执行验证 try { subject.login(usernamePasswordToken); System.out.println("登录成功"); User user = (User)subject.getPrincipal(); System.out.println("登录成功后可以从subject中获取principal信息,principal代表了当前用户对象:"+user.getUserName()); return "index"; } catch (Exception e) { e.printStackTrace(); } System.out.println("登录失败"); return "redirect:/error.html"; } @RequestMapping("loginOut") public String loginOut(){ //得到subject Subject subject = SecurityUtils.getSubject(); subject.logout(); return "redirect:/login.html"; } }请求参数需要组装成一个token,这里是一个用户名和密码串,当我们使用subject调起login时,Shrio会从我们定义的所有Realm中搜寻匹配当前token的Realm来进行认证,当前找到UserRealm做认证。为了简便,userService层login方法直接返回一个User对象,代表成功在数据库中查到用户信息。
public class UserRealm extends AuthorizingRealm{ private UserService userService; public void setUserService(UserService userService) { this.userService = userService; } public UserService getUserService() { return userService; } //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("Shrio执行认证方法"); //等到要认证串的用户名和密码 String username = ((UsernamePasswordToken)token).getUsername(); String password = new String(((UsernamePasswordToken) token).getPassword()); //调用业务层查询数据库 User user = userService.login(username, password); if(user!=null) { //principle 主体/用户名 //credentials 密码 //realmName realm名称 return new SimpleAuthenticationInfo(username,password,"UserRealm"); } System.out.println("用户名或者密码错误"); return null; } }
到此,认证部分已经完成,通过测试,如果当前用户没有被认证(登录),就会拦截当前请求到登录页面,直到用户成功登录后,Shrio不会再拦截已经登录过的用户的请求。
实现Shrio的授权:
授权就是通过设定规则,指定哪些URL需要哪些权限可以访问资源,修改shrio配置文件
/custom.html 需要用户权限,/manager.html需要管理员和超级管理员两个权限,是And的关系。
<!--配置login.do、error.html为不需要认证的URL,配置哪些资源需要哪些权限才能访问 anon为不需要认证和授权都可以访问 perms为需要什么权限才能访问资源 authc为需要认证后才能访问资源 注意:一定要按照以上的顺序写配置,不然权限拦截会失败 --> <property name="filterChainDefinitions"> <value> /error.html = anon /login.html = anon /login.do = anon /custom.html = perms["用户"] /manager.html = perms["管理员","超级管理员"] /*.do = authc /*.html = authc </value> </property>
在之前的自定义的UserRealm的授权方法给当前认证的用户授权,给予当前认证的用户授予‘用户’的权限,当用户已经认证后,访问custom.html成功,但是访问manager.html失败。
//授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("Shrio执行授权方法"); //获取当前主体的primaryPrincipal(当前主体已经被认证) String primaryPrincipal = (String)principals.getPrimaryPrincipal(); System.out.println("当前已经登录认证的用户是"+primaryPrincipal); //根据身份primaryPrincipal获取权限信息,通常这里查询数据库,这里模拟当前用户的权限查到为用户 //封装简单的授权信息对象 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); ArrayList<String> permisions = new ArrayList<>(); permisions.add("用户"); simpleAuthorizationInfo.addStringPermissions(permisions); return simpleAuthorizationInfo; }
如果想让/manager.html可以被管理员或者(OR)超级管理员其一访问,那么就要自定义授权过滤器,实际上就相当于改变了过滤器的规则,像上面perms就代表了一种过滤器。
写一个自定义的过滤器MyAuthorizationFilter:
public class MyAuthorizationFilter extends AuthorizationFilter{ @Override protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception { //获取配置文件中声明的权限列表 String[] perms = (String[])o; //获取主体 Subject subject = getSubject(servletRequest, servletResponse); //改变授权逻辑(权限有其中之一也可以访问资源) if(perms==null||perms.length==0) { return true; } for (String perm : perms) { if(subject.isPermitted(perm)) { return true; } } return false; }
修改配置文件
<!--自定义授权过滤器--> <bean id="myAuthorizationFilter" class="work.yanghao.filter.MyAuthorizationFilter"></bean>
<!--Shrio过滤器工厂Bean 通过属性注入的方式注入--> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" depends-on="myAuthorizationFilter"> <!--安全管理器--> <property name="securityManager" ref="securityManager"/> <!--认证相关配置:当用户没有登陆访问资源就会跳转到此页面--> <property name="loginUrl" value="/login.html"/> <!--授权相关配置:当用户没有访问权限的资源就跳转到次页面--> <property name="unauthorizedUrl" value="/error.html"/> <!--perms为过滤器的字段名称,这里配置会顶替内置的perms过滤器--> <property name="filters"> <map> <entry key="perms" value-ref="myAuthorizationFilter"></entry> </map> </property> <!--过滤链:针对不同url指定不同的过滤器--> <!--配置login.do、error.html为不需要认证的URL,配置哪些资源需要哪些权限才能访问 anon为不需要认证和授权都可以访问 perms为需要什么权限才能访问资源 authc为需要认证后才能访问资源 注意:一定要按照以上的顺序写配置,不然权限拦截会失败 --> <property name="filterChainDefinitions"> <value> /error.html = anon /login.html = anon /login.do = anon /html/custom.html = perms["用户"] /html/manager.html = perms["管理员","超级管理员"] /*.do = authc /*.html = authc /* = authc </value> </property> </bean>
到此,权限规则则交给我们自定义的过滤器来实现。
--------------------------------------------------------------------------------------------------------------------------------
以上都是基于URL进行权限控制的,想对程序实现更加精细的控制可以使用Shiro的注解来实现:
待续。。。。。。。。。
项目源码地址参考:https://github.com/dlyanghao/ShrioDemo