1.10分钟带你轻松入门shiro
Shiro是apache旗下的一款轻量级的Java安全框架,它可以提供如下服务:
Authentication(认证)
Authorization(授权)
Session Management(会话管理)
Cryptography(加密)
接下来我们来一起进入shiro的世界吧
第一步: 在pom.xml文件中添加以下依赖:
<!-- Shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
第二步: 在classpath文件下添加shiro.ini文件:
[users]
shiro = 201314
第三步:认识一下shiro的认证服务,写一个main方法测试一下:
package com.simple.shiro;
import org.apache.log4j.Logger;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
/**
* @author xiaobianchen
* @version 1.0 2016/4/19
*/
public class HelloShiro {
private static final Logger logger = Logger.getLogger(HelloShiro.class);
public static void main(String[] args) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//获取当前用户
Subject currentUser = SecurityUtils.getSubject();
//登录
UsernamePasswordToken token = new UsernamePasswordToken("shiro", "201314");
try {
currentUser.login(token);
} catch (AuthenticationException e) {
logger.info("login failure!");
return;
}
logger.info("login success! Hello " + currentUser.getPrincipal());
//注销
currentUser.logout();
}
}
我们分析一下这个 HelloShiro 吧:
需要读取 classpath 下的 shiro.ini 配置文件,并通过工厂类创建 SecurityManager 对象,最终将其放入 SecurityUtils 中,供 Shiro 框架随时获取。
同样通过 SecurityUtils 类获取 Subject 对象,其实就是当前用户,只不过在 Shiro 的世界里优雅地将其称为 Subject(主体)。
首先使用一个 Username 与 Password,来创建一个 UsernamePasswordToken 对象,然后我们通过这个 Token 对象调用 Subject 对象的 login 方法,让 Shiro 进行用户身份认证。
当登录失败时,您可以使用 AuthenticationException 来捕获这个异常;当登录成功时,您可以调用 Subject 对象的 getPrincipal 方法来获取 Username,此时 Shiro 已经为您创建了一个 Session。
最后还是通过 Subject 对象的 logout 方法来注销本次 Session。
shiro的内部调用流程如下:
2.Web开发中使用shiro
Shiro官方提供的Web模块-shiro-web
你只需要在pom.xml文件中添加如下配置:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency>
然后在web.xml文件中添加Listener与一个Filter
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
EnvironmentLoaderListener这个监听器是用来初始化SecurityManager的,并且通过ShiroFilter来完成验证和授权的
在数据库中建立如下面的表
在shiro.ini配置:
[main]
authc.loginUrl=/login
ds = org.apache.commons.dbcp.BasicDataSource
ds.driverClassName = com.mysql.jdbc.Driver
ds.url = jdbc:mysql://localhost:3306/test
ds.username = root
ds.password = root
passwordMatcher = org.apache.shiro.authc.credential.PasswordMatcher
jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.dataSource = $ds
jdbcRealm.authenticationQuery = select password from user where username = ?
jdbcRealm.userRolesQuery = select r.role_name from user u, user_role ur, role r where u.id = ur.user_id and r.id = ur.role_id and u.username = ?
jdbcRealm.permissionsQuery = select p.permission_name from role r, role_permission rp, permission p where r.id = rp.role_id and p.id = rp.permission_id and r.role_name = ?
jdbcRealm.permissionsLookupEnabled = true
jdbcRealm.credentialsMatcher = $passwordMatcher
securityManager.realms = $jdbcRealm
cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $cacheManager
[urls]
/ = anon
/space/** = authc
对以上配置解释如下:
首先,在 [main] 片段中,我们定义了一个“authc.loginUrl=/login”,用于配置当需要认证时需要跳转的 URL 地址,这里表示重定向到 /login 请求,通过 Servlet 映射后可定位到 login 页面。
然后,定义了一个 DBCP 的 DataSource,用于获取 JDBC 数据库连接。
然后,定义 JdbcRealm 并指定 DataSource,通过配置以下几条 SQL 来完成认证与授权:
authenticationQuery:该 SQL 语句用于提供身份认证,即通过 username 查询 password。
userRolesQuery:该 SQL 语句用于提供基于角色的授权验证(属于粗粒度级别),即通过 username 查询 role_name。
permissionQuery:该 SQL 语句用于提供基于权限的授权验证(属于细粒度级别),即通过 role_name 查询 permission_name,此时需要开启 permissionsLookupEnabled 开关,默认是关闭的。
最后,在 [urls] 片段中,我们定义了一系列的 URL 过滤规则,Shiro 已经提供了一些默认的 Filter(过滤器),便于我们随时使用,当然您也可以扩展其它的过滤器。就本例配置而言,解释如下:
/ = anon:对于“/”请求(首页)可以匿名访问的。
/space/** = authc:对于以“/space/”开头的请求,均由 authc 过滤器处理,也就是完成身份认证操作。
每次认证都需要与数据库打交道,所以基于性能的考虑 在这里我们配置了cacheManager
那么对于我们新建一个index.jsp页面
<%@ page pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
<head>
<title>首页</title>
</head>
<body>
<h1>首页</h1>
<shiro:guest>
<p>身份:游客</p>
<a href="<c:url value="/login"/>">登录</a>
<a href="<c:url value="/register"/>">注册</a>
</shiro:guest>
<shiro:user>
<p>身份:<shiro:principal/></p>
<a href="<c:url value="/space"/>">空间</a>
<a href="<c:url value="/logout"/>">退出</a>
</shiro:user>
</body>
</html>
基于servlet服务器的代码:
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private UserService userService = new UserServiceImpl();
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 获取表单数据
String username = request.getParameter("username");
String password = request.getParameter("password");
boolean isRememberMe = request.getParameter("rememberMe") != null;
// 调用登录服务
try {
userService.login(username, password, isRememberMe);
} catch (LoginException e) {
request.setAttribute("exception", e.getName());
return;
}
// 重定向到空间页面
response.redirect("/index")
}
}
关于shiro-web目前就介绍这么多了
3.Spring与shiro的集成
spring与shiro的集成 首先需要在pom.xml中添加依赖:
<!-- Shiro Spring-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
web.xml中做如下配置:
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<!-- 读取配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:conf/spring-mybatis.xml
classpath:conf/spring-shiro.xml
</param-value>
</context-param>
<!--字符集过滤器-->
<filter>
<filter-name>characterEncodingFilter</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>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--springmvc 核心配置-->
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<description>SpringContext</description>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:conf/mvc-dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 配置主页 -->
<welcome-file-list>
<welcome-file>/WEB-INF/jsp/login.jsp</welcome-file>
</welcome-file-list>
<!-- 配置session超时时间,单位分钟 -->
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<!--配置错误页面-->
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/jsp/error/404.jsp</location>
</error-page>
</web-app>
最重要的spring-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.xsd">
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<property name="filterChainDefinitions">
<value>
/ = anon
/space/** = authc
</value>
</property>
</bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="jdbcRealm"/>
<property name="cacheManager" ref="cacheManager"/>
</bean>
<bean id="jdbcRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
<property name="dataSource" ref="dataSource"/>
<property name="authenticationQuery" value="select password from user where username = ?"/>
<property name="userRolesQuery" value="select r.role_name from user u, user_role ur, role r where u.id = ur.user_id and r.id = ur.role_id and u.username = ?"/>
<property name="permissionsQuery" value="select p.permission_name from role r, role_permission rp, permission p where r.id = rp.role_id and p.id = rp.permission_id and r.role_name = ?"/>
<property name="permissionsLookupEnabled" value="true"/>
<property name="cacheManager" ref="cacheManager"/>
<property name="credentialsMatcher" ref="passwordMatcher"/>
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!--缓存-->
<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"/>
<!--密码加密-->
<bean id="passwordMatcher" class="org.apache.shiro.authc.credential.PasswordMatcher"/>
<bean id="passwordService" class="org.apache.shiro.authc.credential.DefaultPasswordService"/>
</beans>
登录页面建一个jsp页面:
<%@ page pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<h1><a href="<c:url value="/"/>">首页</a> - 登录</h1>
<shiro:notAuthenticated>
<form action="<c:url value="/signIn"/>" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"/></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"/></td>
</tr>
<tr>
<td></td>
<td>
<label><input type="checkbox" name="rememberMe"/>记住我</label>
</td>
</tr>
<tr>
<td></td>
<td>
<button type="submit">登录</button>
</td>
</tr>
</table>
</form>
<c:if test="${requestScope['exception'] == 'LoginException'}">
<p>登录失败!</p>
</c:if>
</shiro:notAuthenticated>
<shiro:authenticated>
<c:redirect url="/space"/>
</shiro:authenticated>
</body>
</html>
控制器层:
@Controller
public class LoginController {
private static final Logger LOG = LoggerFactory.getLogger(LoginController.class);
@RequestMapping(value = "/index",method = RequestMethod.GET)
public String index(){
return "index";
}
@Autowired
private IUserService userService;
@RequestMapping(value = "/signIn",method = RequestMethod.GET)
public String signIn(){
return "signIn";
}
@RequestMapping(value = "/signIn",method = RequestMethod.POST)
public String submit(HttpServletRequest request) {
// 获取表单数据
String username = request.getParameter("username");
String password = request.getParameter("password");
boolean isRememberMe = request.getParameter("rememberMe") != null;
// 调用登录服务
try {
userService.login(username, password);
} catch (LoginException e) {
request.setAttribute("exception", e.getName());
return signIn();
}
// 重定向到主页面
return "redirect:/index";
}
}