Shiro总结及SSM框架通过Shiro实现登录认证及用户授权

权限管理,可以实现用户访问系统的控制,权限管理分为认证和授权

Shiro(Apache Shiro)是Java的一个安全框架,Shiro属于轻量级框架,可以实现认证、授权、加密、会话管理、与Web集成、缓存等

Shiro核心类示意图
在这里插入图片描述
主要类:

Authentication: 身份认证

Authorization: 授权,即权限验证,验证某个已认证用户是否拥有某个权限

Seesion Manager: 会话管理,每次用户登录都是一次会话,在没有退出之前,他的所有信息都在会话中

Cryptography: 加密,保护数据的安全性

Web Support: Web支持,可以容易的集成到Web环境中

Catching: 缓存,用户登录后,其用户信息、所拥有的角色、权限不用每次去查,可以提高效率

Concurrency: shiro支持多线程应用的并发验证,即在一个线程中开启另一个线程,能把权限自动传播过去

Testing: 提供测试支持

Run As: 允许一个用户以另一个用户的身份进行访问

Remember Me: 记住我,即一次登录后,下次就不用登录了

Shiro架构图:
在这里插入图片描述

主要组件:

Subject: 主体,主体可以是用户也可以是程序,主体要访问系统,系统需要对主体进行认证、授权。

SecurityManager: 安全管理器,主体进行认证和授权都是通过securityManager进行

Authenticator: 认证器,主体进行认证最终通过Authenticator进行的。

Authorizer: 授权器,主体进行授权最终通过authenticator进行的。

SessionManager: 会话管理,web应用中一般是用web容器对session进行管理,Shiro也提供一套session管理的方式。

SessionDao: 通过SessionDao管理Session数据

CacheManager: 缓存管理器,主要对Session和授权数据进行缓存

Realm: 领域,相当于数据源,通过Realm存取认证、授权相关数据

Cryptography: 密码管理,提供一套加密/解密的组件,便于开发

登录认证

1. 整合Shiro,添加Shiro所需要的pom依赖
<dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-nop</artifactId>
      <version>1.7.24</version>
    </dependency>
    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2.1</version>
    </dependency>
    <!--    shiro核心jar包-->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.4.0</version>
    </dependency>
    <!--    shiro整合到web工程所依赖的jar包-->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>1.4.0</version>
    </dependency>
     <!--    shiro缓存依赖的jar包-->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-ehcache</artifactId>
      <version>1.4.0</version>
    </dependency>
       <!--    shiro整合与Spring的整合-->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.4.0</version>
    </dependency>
2. 在spring当中配置shiro过滤器和安全管理器
		 <!-- 配置shiro过滤器 -->
		    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		        <!--配置登录认证的路径
        			如果请求是login,则做去执行认证操作(调用对应的Realm执行认证操作);
       				 如果请求不是login请求,且没有认证,则会直接执行login请求(下面贴有login请求对应的方法)
        		-->
        		<property name="loginUrl" value="/login"/>
		        <property name="securityManager" ref="securityManager"></property>
		
		        <!-- 配置shiro过滤器pattern -->
		        <property name="filterChainDefinitions">
		            <value>
		                /static/** = anon   <!--不需要登录验证-->
		                /login.jsp = anon   <!--不需要登录验证-->
		                /**=authc     <!--除指定请求外,其它所有的请求都需要身份验证-->
		            </value>
		        </property>
		    </bean>
		
		    <!-- 配置shiro安全管理器 -->
		    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"></bean>

login请求对应的控制器方法

		@Controller
		public class LoginController {
    		@RequestMapping("/login")
    		public String login() {
        	return "redirect:login.jsp";
   			} 
		}
3. 在web.xml当中配置过滤器拦截所有请求,进行处理
 <!-- 拦截到所有请求,使用spring一个bean(ShiroFilter)来进行处理 -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <!-- 是否filter中的init和 destroy-->
        <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>
4. 配置及编写用于认证操作的Realm

把自己写的自定义Realm交给Spring管理

	<!--自定义realm-->
    <!-- 配置realm数据源 -->
     <!-- class为自己编写的realm -->
    <bean id="employeeRealm" class="com.eh.web.realm.EmployeeRealm"></bean>

并将Realm配置到安全管理器中

    <!-- 配置shiro安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="employeeRealm"/>
<!--        注入缓存-->
        <property name="cacheManager" ref="ehCache"/>
    </bean>

自定义realm编写认证操作

public class EmployeeRealm extends AuthorizingRealm {

    @Autowired
    private EmployeeService employeeService;
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("来到了认证");
        //获取表单传来的参数(表单传的参数已经被封装到token中)
        String username = (String) token.getPrincipal();
        System.out.println(username);
        //到数据库中查询有没有当前用户
        Employee employee = employeeService.getEmployeeWithName(username);
        System.out.println(employee);
        if (employee == null) {
            //没有该用户
            return null;
        }
        //认证
        //参数: 主体(当前用户) 正确的密码,盐(此处省略没写,下面会单独介绍),当前realm名称
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(employee, employee.getPassword(), this.getName());
        //将认证信息返回(成功还是失败) 定义一个过滤器,用来监听该认证是否成功
        return info;
    }
 }
5. 编写用于监听认证是否成功的过滤器

将该过滤器交给Spring管理

	<!-- 配置自定义的监听的过滤器-->
    <bean id="myFormFilter" class="com.eh.web.filter.MyFormFilter"></bean>
    
        <!-- 配置shiro过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!--配置登录认证的路径
        	如果请求是login,则做去执行认证操作(调用对应的Realm执行认证操作);
    		 如果请求不是login请求,且没有认证,则会直接执行login请求(下面贴有login请求对应的方法)
   		-->
        <property name="loginUrl" value="/login"/>

		<!--将监听的过滤器配置到shiro中-->
        <property name="filters">
            <map>
                <entry key="authc" value-ref="myFormFilter"/>
            </map>
        </property>

        <!-- 配置shiro安全管理器 -->
    	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
      		  <property name="realm" ref="employeeRealm"/>
    	</bean>
        <!-- 配置shiro过滤器pattern -->
        <property name="filterChainDefinitions">
            <value>
                /static/** = anon   <!--不需要登录验证-->
                /login.jsp = anon   <!--不需要登录验证-->
                /logout = logout   <!--取消认证-->
                /**=authc     <!--除指定请求外,其它所有的请求都需要身份验证-->
            </value>
        </property>
    </bean>
//表单认证过滤器
public class MyFormFilter extends FormAuthenticationFilter {
	//拦截成功
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        System.out.println("认证成功");
        AjaxRes ajaxRes = new AjaxRes();
        ajaxRes.setSuccess(true);
        ajaxRes.setMsg("登录成功");
        //把对象转换成json类型的字符串(前端请求使用的ajax请求)
        String JsonString = new ObjectMapper().writeValueAsString(ajaxRes);
        //设置字符集编码
        response.setCharacterEncoding("utf-8");
        //相应给浏览器
        response.getWriter().print(JsonString);
        //返回false  表示不需要执行下一个拦截器
        return false;
    }
    //拦截失败
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        System.out.println("认证失败");
        AjaxRes ajaxRes = new AjaxRes();
        //根据不同的异常相应不同的信息
        if (e != null) {
            //获取异常名称
            String name = e.getClass().getName();
            if (name.equals(UnknownAccountException.class.getName())) {
                //账号错误
                ajaxRes.setSuccess(false);
                ajaxRes.setMsg("账号不存在");
            } else if (name.equals(IncorrectCredentialsException.class.getName())) {
                //密码错误
                ajaxRes.setSuccess(false);
                ajaxRes.setMsg("密码错误");
            } else {
                //未知异常
                ajaxRes.setSuccess(false);
                ajaxRes.setMsg("未知错误");
            }
        }
        //设置字符集编码
        response.setCharacterEncoding("utf-8");
        try {
            //把对象转换成json类型的字符串
            String JsonString = new ObjectMapper().writeValueAsString(ajaxRes);
            response.setCharacterEncoding("utf-8");
            //相应给浏览器
            response.getWriter().print(JsonString);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        //返回false  表示不需要执行下一个拦截器
        return false;
    }
  }

浏览器请求及相应转发代码

$.post("login", $("form").serialize(), function (data) {
                    //把json格式的字符串转换成json数据
                    data = $.parseJSON(data)
                    if (data.success) {
                        //成功
                        window.location.href = "${pageContext.request.contextPath}/index.jsp";
                    } else {
                        alert(data.msg);
                    }
                })

用户退出功能的实现:
前端界面:

<a style="font-size: 18px; color: white;text-decoration: none;" href="${pageContext.request.contextPath}/logout">注销</a>

在shiro过滤器中添加logout的处理

<!-- 配置shiro过滤器pattern -->
        <property name="filterChainDefinitions">
            <value>
                /static/** = anon   <!--不需要登录验证-->
                /login.jsp = anon   <!--不需要登录验证-->
                /logout = logout   <!--取消认证-->
                /**=authc     <!--除指定请求外,其它所有的请求都需要身份验证-->
            </value>
        </property>

这样整个认证登录过程就实现实现了

用户授权

授权方法调用的时机:
  1. 有shiro标签的使用
<shiro:hasPermission name="employee:delete">
        <a href="#" class="easyui-linkbutton" data-options="iconCls:'icon-remove',plain:true" id="delete">离职</a>
    </shiro:hasPermission>
  1. 有权限(shiro)的注解
	@RequiresPermissions("employee:index")
    @RequestMapping("/employee")
    public String employee() {
        return "employee";
    }

注:在使用shiro注解时,需要在配置文件汇总添加shiro的注解扫描:

<!--
    配置为true即使用cglib继承的方式,
    false为jdk的接口动态代理   控制器没有实现接口
    -->
    <aop:config proxy-target-class="true" ></aop:config>

    <!-- 使用第三方去扫描shiro的注解 -->
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor ">
        <property name="securityManager" ref="securityManager"></property>
    </bean>

授权方法

 //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
        System.out.println("授权");
        Employee employee = (Employee) principal.getPrimaryPrincipal();
        //查询角色和对应的权限
        List<String> roles = new ArrayList<>();
        List<String > permissions = new ArrayList<>();

        //判断当前用户是否是管理员, 如果是管理员则拥有所有权限
        if (employee.getAdmin()) {
            //拥有所有权限
            permissions.add("*:*");
        } else {
            //查询角色
            roles = employeeService.getRolesById(employee.getId());
            System.out.println("------------------" + roles);
            //查询权限
            permissions = employeeService.getPermissionsById(employee.getId());
            System.out.println("------------------" + permissions);
        }
        //赋给授权信息
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRoles(roles);
        info.addStringPermissions(permissions);
        return info;
    }

没有权限结果处理,当没有权限访问某一页面时,会报500错误,这时候可以定义一个处理没有权限的方法

 	//未授权异常
    @ExceptionHandler(AuthorizationException.class)
    public void ExceptionHandler(HandlerMethod method, HttpServletResponse response) throws IOException {//拿取发生异常的方法
        //判断当前请求是不是Json(AJAX)请求,如果是(则在该方法中无法实现跳转),返回json数据给浏览器,让其自己做跳转
        //获取发生异常的方法
        //获取方法上的注解,通过方法上是否有@ResponseBody注解来判断
        ResponseBody responseBody = method.getMethodAnnotation(ResponseBody.class);
        if (responseBody != null) {
            //则该请求为(Ajax)json请求
            AjaxRes ajaxRes = new AjaxRes();
            ajaxRes.setMsg("没有权限操作");
            ajaxRes.setSuccess(false);
            String s = new ObjectMapper().writeValueAsString(ajaxRes);
            response.setCharacterEncoding("utf-8");
            response.getWriter().print(s);
        } else {
            response.sendRedirect("nopermission.jsp");
        }
    }

这样就实现了用户授权

补充:

Shiro还有一些比较常用但前面还没有介绍的业务,这里补充说明一下

Shiro缓存的使用
  1. 所需要的pom依赖
<dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-ehcache</artifactId>
      <version>1.2.2</version>
</dependency>
  1. 添加shiro缓存配置
    添加shiro-ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
	<defaultCache 
		maxElementsInMemory="1000" 
		maxElementsOnDisk="10000000"
		eternal="false" 
		overflowToDisk="false" 
		diskPersistent="false"
		timeToIdleSeconds="120"
		timeToLiveSeconds="120" 
		diskExpiryThreadIntervalSeconds="120"
		memoryStoreEvictionPolicy="LRU">
	</defaultCache>
</ehcache>
  1. 将该缓存配置添加到安全管理器(SecurityManager)中
    <!-- 配置shiro安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="employeeRealm"/>
		<!--注入缓存-->
        <property name="cacheManager" ref="ehCache"/>
    </bean>
Shiro密码加密
  1. 添加凭证匹配器
<!-- 凭证匹配器 -->
    <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <!-- 散列算法 -->
        <property name="hashAlgorithmName" value="md5"/>
        <!-- 散列次数 -->
        <property name="hashIterations" value="2"></property>
    </bean>

并将该凭证匹配器配置到自定义Realm(数据源)中

	<!--自定义realm-->
    <!-- 配置realm数据源 -->
    <bean id="employeeRealm" class="com.eh.web.realm.EmployeeRealm">
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
    </bean>
  1. 在保存用户时, 给用户密码进行加密处理
	@Override
    public void saveEmployee(Employee employee) {
        //把密码进行加密,把该用户名当做盐 散列次数2
        Md5Hash md5Hash = new Md5Hash(employee.getPassword(), employee.getUsername(), 2);
        employee.setPassword(md5Hash.toString());
        //保存员工
        employeeMapper.insert(employee);
        //保存关系表
        for (Role role : employee.getRoles()) {
            employeeMapper.insertEmployeeAndRoleRel(employee.getId(), role.getRid());
        }
    }

  1. 认证时,添加密码处理
 		//参数 正确的密码,盐,当前realm名称
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(employee, employee.getPassword(), ByteSource.Util.bytes(employee.getUsername()), this.getName());
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章