可以参考Apache Shiro官网的文档:http://shiro.apache.org/reference.html
代码下载地址:https://download.csdn.net/download/qq_42969074/10810610
Apache Shiro是一个功能强大且灵活的开源安全框架,可以清晰地处理身份验证,授权,企业会话管理和加密。
Apache Shiro的首要目标是易于使用和理解。安全有时可能非常复杂,甚至是痛苦的,但并非必须如此。框架应尽可能掩盖复杂性,并提供简洁直观的API,以简化开发人员确保其应用程序安全的工作。
最先创建三个表:用户表(t_user),角色表(t_role),权限表(t_permission):
创建自定义MyRealm
类
有关Shiro的基础知识我这里就不过多介绍了,直接来干货,到最后会整合Spring来进行权限验证。
首先在使用Shiro的时候我们要考虑在什么样的环境下使用:
- 登录的验证
- 对指定角色的验证
- 对URL的验证
基本上我们也就这三个需求,所以同时我们也需要三个方法:
getUserByUserName(String username)
根据username查询用户,之后Shiro会根据查询出来的User的密码来和提交上来的密码进行比对。getRoles(String username)
根据username查询该用户的所有角色,用于角色验证。getPermissions(String username)
根据username查询他所拥有的权限信息,用于权限判断。
下面贴一下代码
Dao接口代码:
public interface UserDao {
/**
* 通过用户名查询用户
* @param userName
* @return
*/
public User getByUserName(String userName);
/**
* 通过用户名查询角色信息
* @param userName
* @return
*/
public Set<String> getRoles(String userName);
/**
* 通过用户名查询权限信息
* @param userName
* @return
*/
public Set<String> getPermissions(String userName);
}
mapper中的代码:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.thr.dao.UserDao">
<resultMap type="User" id="UserResult">
<result property="id" column="id"/>
<result property="userName" column="userName"/>
<result property="password" column="password"/>
</resultMap>
<select id="getByUserName" parameterType="String" resultMap="UserResult">
select * from t_user where userName=#{userName}
</select>
<select id="getRoles" parameterType="String" resultType="String">
select r.roleName from t_user u,t_role r where u.roleId=r.id and u.userName=#{userName}
</select>
<select id="getPermissions" parameterType="String" resultType="String">
select p.permissionName from t_user u,t_role r,t_permission p where u.roleId=r.id and p.roleId=r.id and u.userName=#{userName}
</select>
</mapper>
很简单只有三个方法,分别对应上面所说的三个方法。实体类就比较简单了,就只有字段以及get,set方法。我就这里就不贴了。
现在就需要创建自定义的MyRealm
类,这个还是比较重要的。继承至Shiro
的AuthorizingRealm
类,用于处理自己的验证逻辑,下面贴一下Realm的代码:
package com.thr.realm;
import javax.annotation.Resource;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import com.thr.entity.User;
import com.thr.service.UserService;
public class MyRealm extends AuthorizingRealm{
@Resource
private UserService userService;
/**
* 为当前登录的用户授予角色和权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName=(String) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorization=new SimpleAuthorizationInfo();
authorization.setRoles(userService.getRoles(userName));
authorization.setStringPermissions(userService.getPermissions(userName));
return null;
}
/**
* 验证当前登录的用户
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName=(String) token.getPrincipal();
User user=userService.getByUserName(userName);
if(user!=null){
AuthenticationInfo authcInfo=new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), "xx");
return authcInfo;
}
return null;
}
}
继承AuthorizingRealm
类之后就需要覆写它的两个方法,doGetAuthorizationInfo
,doGetAuthenticationInfo
,这两个方法的作用我都有写注释,逻辑也比较简单。doGetAuthenticationInfo
是用于登录验证的,在登录的时候需要将数据封装到Shiro
的一个token
中,执行shiro的login()
方法,之后只要我们将MyRealm
这个类配置到Spring中,登录的时候Shiro
就会自动的调用doGetAuthenticationInfo()
方法进行验证。
贴下登录的Controller
:
package com.thr.controller;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.thr.entity.User;
@Controller
@RequestMapping("/")
public class UserController {
/**
* 用户登录
* @param user
* @param request
* @return
*/
@RequestMapping("/login")
public String login(User user,HttpServletRequest request){
Subject subject=SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(user.getUserName(), user.getPassword());
try{
subject.login(token);
Session session=subject.getSession();
System.out.println("sessionId:"+session.getId());
System.out.println("sessionHost:"+session.getHost());
System.out.println("sessionTimeout:"+session.getTimeout());
session.setAttribute("info", "session的数据");
return "redirect:/success.jsp";
}catch(Exception e){
e.printStackTrace();
request.setAttribute("user", user);
request.setAttribute("errorMsg", "用户名或密码错误!");
return "index";
}
}
@RequestMapping("/admin")
public String admin(HttpServletRequest request) {
return "success";
}
@RequestMapping("/student")
public String student(HttpServletRequest request) {
return "success";
}
@RequestMapping("/teacher")
public String teacher(HttpServletRequest request) {
return "success";
}
}
主要就是login()
方法。逻辑比较简单,只是登录验证的时候不是像之前那样直接查询数据库然后返回是否有用户了,而是调用subject
的login()
方法,就是我上面提到的,调用login()
方法时Shiro
会自动调用我们自定义的MyRealm
类中的doGetAuthenticationInfo()
方法进行验证的,验证逻辑是先根据用户名查询用户,如果查询到的话再将查询到的用户名和密码放到SimpleAuthenticationInfo
对象中,Shiro会自动根据用户输入的密码和查询到的密码进行匹配,如果匹配不上就会抛出异常,匹配上之后就会执行doGetAuthorizationInfo()
进行相应的权限验证。doGetAuthorizationInfo()
方法的处理逻辑也比较简单,根据用户名获取到他所拥有的角色以及权限,然后赋值到SimpleAuthorizationInfo
对象中即可,Shiro就会按照我们配置的XX角色对应XX权限来进行判断,这个配置在下面的整合中会讲到。
整合Spring
接下来应该是大家比较关系的一步:整合Spring
。
我是在之前的Spring SpringMVC Mybatis
的基础上进行整合的。
web.xml配置
首先我们需要在web.xml
进行配置Shiro的过滤器。
我只贴Shiro部分的,其余的和之前配置是一样的。
<?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>ShiroWeb2</display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- 配置shiro过滤器 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targeFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置spring文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- spring监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置spring编码格式 -->
<filter>
<filter-name>encodingFilter</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>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置springMVC文件 -->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
配置还是比较简单的,这样会过滤所有的请求。
之后我们还需要在Spring中配置一个shiroFilter
的bean。
applicationContext.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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- 自动扫描 -->
<context:component-scan base-package="com.thr.service"></context:component-scan>
<!-- 加载jdbc.properties -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 配置Mybatis的sqlsessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="dataSource" ref="dataSource"></property>
<property name="mapperLocations" value="classpath:com/thr/mapper/*.xml"></property>
</bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.thr.dao"></property>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
<!-- 自定义Realm -->
<bean id="myRealm" class="com.thr.realm.MyRealm"></bean>
<!-- shiro安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"></property>
</bean>
<!-- 配置shiro过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- shiro的核心安全接口,这个是必须的 -->
<property name="securityManager" ref="securityManager"></property>
<!-- 身份认证失败,则跳转到登录页面配置 -->
<property name="loginUrl" value="/index.jsp"></property>
<!-- 权限认证失败,则跳到指定页面 -->
<property name="unauthorizedUrl" value="/unautor.jsp"></property>
<!-- 配置shiro连接约束配置,即过滤链的定义 -->
<property name="filterChainDefinitions">
<value>
<!-- 游客身份,多可以访问 -->
/login=anon
<!-- 必须进行身份认证 -->
/admin*=authc
<!-- 要有teacher的角色 -->
/student=roles[teacher]
/teacher=perms["user:create"]
</value>
</property>
</bean>
<!-- 保证实现了shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean>
<!-- 开启Shiro注解 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!-- spring事物 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="edit*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="new*" propagation="REQUIRED" />
<tx:method name="set*" propagation="REQUIRED" />
<tx:method name="remove*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="change*" propagation="REQUIRED" />
<tx:method name="get*" propagation="REQUIRED" read-only="true" />
<tx:method name="find*" propagation="REQUIRED" read-only="true" />
<tx:method name="load*" propagation="REQUIRED" read-only="true" />
<tx:method name="*" propagation="REQUIRED" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- 配置事物切面 -->
<aop:config>
<aop:pointcut expression="execution(* com.thr.service.*.*(..))" id="serviceOperation"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation"/>
</aop:config>
</beans>
这个JSP中的代码:这里有3个jsp页面,我将它们写在一起了。
//index.jsp
<body>
<form action="${pageContext.request.contextPath }/login.do" method="post">
userName:<input type="text" name="userName" value="${user.userName }"/><br/>
password:<input type="password" name="password" value="${user.password }"><br/>
<input type="submit" value="login"/><font color="red">${errorMsg }</font>
</form>
</body>
//success.jsp
<body>
登录成功
</body>
//unauthor.jsp
<body>
<h1>授权失败!</h1>
</body>
在这里我们配置了上文中所提到的自定义myRealm
,这样Shiro就可以按照我们自定义的逻辑来进行权限验证了。其余的都比较简单,看注释应该都能明白。
着重讲解一下:
<property name="filterChainDefinitions">
<value>
<!--anon 表示匿名访问,不需要认证以及授权-->
/login=anon
<!--authc表示需要认证 没有进行身份认证是不能进行访问的-->
/admin*=authc
/student=roles[teacher]
/teacher=perms["user:create"]
</value>
</property>
1、/login=anon的意思的意思是,发起/login这个请求是不需要进行身份认证的,这个请求在这次项目中是一个登录请求,一般对于这样的请求都是不需要身份认证的。
2、/admin*=authc表示 /admin,/admin1,/admin2这样的请求都是需要进行身份认证的,不然是不能访问的。
3、/student=roles[teacher]表示访问/student请求的用户必须是teacher
角色,不然是不能进行访问的。
4、/teacher=perms[“user:create”]表示访问/teacher请求是需要当前用户具有user:create
权限才能进行访问的
测试:
首先来验证一下登录:由于上面配置了/login=anon 所有login不会被拦截。
接着我们来访问一下/student
这个请求,因为在Spring的配置文件中:
<property name="filterChainDefinitions">
<value>
<!--anon 表示匿名访问,不需要认证以及授权-->
/login=anon
<!--authc表示需要认证 没有进行身份认证是不能进行访问的-->
/admin*=authc
/student=roles[teacher]
/teacher=perms["user:create"]
</value>
</property>
只有teacher
角色才能访问/student
这个请求:所有会授权失败!