Shiro学习笔记
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
创建一个简单的helloworld
- 创建maven工程,jar类型
- 添加shiro的jar包
<dependencies> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.12</version> <scope>test</scope> </dependency> </dependencies> |
- 在src/main/resources下创建
log4j.properties直接拷贝其他工程 |
shiro.ini的配置如下
[users] //说明是用户信息 king=123 //是以键值对出现 wang=1234 |
- 创建HelloWorld.java
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.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory;
public class HelloWorld {
public static void main(String[] args) { //读取配置文件,初始化SecurityManager工厂 Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); //获取SecurityManager实例 SecurityManager securityManager = factory.getInstance(); //把SecurityManager实例绑定到SecurityUtils SecurityUtils.setSecurityManager(securityManager); //获取当前执行的用户 Subject currentUser = SecurityUtils.getSubject(); //创建token令牌,用户名/密码 UsernamePasswordToken token = new UsernamePasswordToken("wang", "1234"); try { //身份验证 currentUser.login(token); System.out.println("身份认证成功!"); }catch(AuthenticationException e) { e.printStackTrace(); System.out.println("身份认证失败!"); } //退出登录 currentUser.logout(); } } |
Subject认证主体
Subject认证主体包含两个信息 Principals:身份,可以是用户名,邮件,手机号码等等,用来标识一个登陆主体身份 Credentials:凭证,常见的有密码,数字证书等等。 |
Realm & JDBC Realm
Realm:意思是域,shiro从Realm中获取验证数据 Realm有很多种类,例如常见的jdbc realm, jndi realm, text realm(上面那个helloworld用的就是) |
测试jdbc_realm使用C3P0连接数据库进行认证
- 创建maven工程,jar类型
- 在pom.xml文件中添加shiro,C3P0的jar包
<dependencies> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.12</version> <scope>test</scope> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency> </dependencies> |
- 在src/main/resources下创建
log4j.properties直接拷贝其他工程 |
jdbc_realm.ini配置如下
[main] //如果配置代码需要使用main jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm //设置jdbcRealm dataSource= com.mchange.v2.c3p0.ComboPooledDataSource //设置数据源 dataSource.driverClass=com.mysql.jdbc.Driver //driver dataSource.jdbcUrl=jdbc:mysql://localhost:3306/mydata?characterEncoding=UTF-8&useSSL=true //url dataSource.user=root //用户名 dataSource.password=123 //密码
jdbcRealm.dataSource=$dataSource //相当于jdbcRealm.setDataSource(dataSource) securityManager.realms=$jdbcRealm //相当于securityManager.setRealms(jdbcRealm) |
创建一个数据库mydata , 创建一个数据表,表名为固定名称users,
字段为userName,password,如果名称不同则jdbcRealm会找不到指定的表名和字段名进行比对
|
编写测试类
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.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory;
public class HelloWorld {
public static void main(String[] args) { //读取配置文件,初始化SecurityManager工厂 Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:jdbc_realm.ini"); //获取SecurityManager实例 SecurityManager securityManager = factory.getInstance(); //把SecurityManager实例绑定到SecurityUtils SecurityUtils.setSecurityManager(securityManager); //获取当前执行的用户 Subject currentUser = SecurityUtils.getSubject(); //创建token令牌,用户名/密码 UsernamePasswordToken token = new UsernamePasswordToken("wang", "12345"); try { //身份验证 currentUser.login(token); System.out.println("身份认证成功!"); }catch(AuthenticationException e) { e.printStackTrace(); System.out.println("身份认证失败!"); } //退出登录 currentUser.logout(); }
} |
权限认证核心要素
权限认证,也就是访问控制,即在应用中控制谁能访问哪些资源 在权限认证中,最核心的三个要素是:权限,角色,用户 权限:操作资源的权利,比如访问某个页面,以及对某个模块的数据的添加,修改,删除,查看的权利 角色:是权利的集合,一种角色可以包含多种权限 用户:在shiro中,代表访问系统的用户成为Subject |
测试角色
在src/main/resources下创建shiro_role.ini配置文件
[users] wang=12345,role3,role2 zhangsan=12345,role1
说明:用户名wang,密码:12345,拥有角色role3,role2 |
创建shiro工具类,用于判断身份是否合法
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.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory;
public class ShiroUtils {
public static Subject login(String configFile, String userName, String password) { // 读取配置文件,初始化SecurityManager工厂 Factory<SecurityManager> factory = new IniSecurityManagerFactory(configFile); // 获取SecurityManager实例 SecurityManager securityManager = factory.getInstance(); // 把SecurityManager实例绑定到SecurityUtils SecurityUtils.setSecurityManager(securityManager); // 获取当前执行的用户 Subject currentUser = SecurityUtils.getSubject(); // 创建token令牌,用户名/密码 UsernamePasswordToken token = new UsernamePasswordToken(userName, password); try { // 身份验证 currentUser.login(token); System.out.println("身份认证成功!"); } catch (AuthenticationException e) { e.printStackTrace(); System.out.println("身份认证失败!"); } return currentUser; } } |
在pom.xml文件中添加junit依赖
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> |
测试用户的角色
@Test public void testRoles() { Subject subject = ShiroUtils.login("classpath:shiro_role.ini", "wang", "12345"); //判断一个角色 boolean result = subject.hasRole("role1"); System.out.println(result ? "有role1角色" : "没有role1角色");
//判断集合角色(可以单个判断角色) boolean[] results = subject.hasRoles(Arrays.asList("role1","role2","role3")); System.out.println(results[0] ? "有role1角色" : "没有role1角色"); System.out.println(results[1] ? "有role2角色" : "没有role2角色"); System.out.println(results[2] ? "有role3角色" : "没有role3角色");
//判断集合角色(只能判断都具备的角色) boolean resultAll = subject.hasAllRoles(Arrays.asList("role1","role2")); System.out.println(resultAll ? "role1,role2角色都有" : "role1,role2角色不全具备"); }
@Test public void testCheckRoles() { Subject subject = ShiroUtils.login("classpath:shiro_role.ini", "wang", "12345"); //检查角色,没有返回值,会抛出异常UnauthorizedException subject.checkRole("role1");
//检查角色,如果有一个不符合的角色就异常 subject.checkRoles("role1","role2","role3");
subject.checkRoles(Arrays.asList("role1","role2")); } |
测试用户的权限
在src/main/resources中创建shiro_permission.ini文件
[users] wang=12345,role1 //用户wang,密码12345,权限role1 zhangsan=12345,role2
[roles] role1=user:select //权限user:select查询权限 role2=user:add,user:update,user.delete |
@Test public void testPermitted() { Subject subject = ShiroUtils.login("classpath:shiro_permission.ini", "wang", "12345"); //单个查询权限 System.out.println(subject.isPermitted("user:update") ? "有查询权限" : "没有查询权限"); //测试多个权限 boolean[] results = subject.isPermitted("user:update","user:select","user:add"); System.out.println(results[0] ? "有Update权限" : "没有update权限"); System.out.println(results[1] ? "有select权限" : "没有select权限"); System.out.println(results[2] ? "有add权限" : "没有add权限"); //整体查询权限(权限之间为与的关系,只要一个为false,整个表达式为false) boolean result = subject.isPermittedAll("user:update","user:select","user:add"); System.out.println(result ? "权限全都有" : "权限不完整"); subject.logout(); }
@Test public void testCheckPermitted() { Subject subject = ShiroUtils.login("classpath:shiro_permission.ini", "wang", "12345"); //单个检查是否有权限,如果没有就会抛出UnauthorizedException异常 subject.checkPermission("user:update");
//整体检查权限(权限之间为与关系) subject.checkPermissions("user:update","user:select","user:add");
} |
Shiro+Web验证用户的身份,角色,权限
创建一个maven工程,带框架maven-archetype-webapp1.0
|
创建后的maven工程是这个样子
没有看到src/main/java目录 这时可以在工程上单击右键Build PathàConfigure Build Path
将默认的JRE1.8删除,并重新添加
然后再次添加JRE 应用保存即可在工程中出现src/main/java目录 |
修改pom.xml文件,添加jar包
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!-- commons-logging --> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <!-- shiro-core --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.4</version> </dependency> <!-- shiro-web支持 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.4</version> </dependency> <!-- slf4j-api --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.12</version> </dependency> |
这时我们发现Maven生成的文件夹格式需要调整
缺少META-INF文件夹,以及文件夹下的MANIFEST.MF文件 以及WEB-INF文件夹下的web.xml的内容也有问题 这时我们可以创建一个临时的动态Web工程,将WebContent下的META-INF文件夹,以及文件夹下的MANIFEST.MF文件拷贝到这个工程的webapp下即可,将临时动态工程的web.xml文件赋值一份到WEB-INF文件夹下替换即可。 标准的格式如下图
|
修改web.xml文件,添加Shiro过滤器和监听
<!-- Shiro-Web项目的入口 --> <listener> <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class> </listener>
<!-- Shiro支持 --> <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> |
创建shiro.ini文件
[main] authc.loginUrl=/login //authc认证登录的url是/login [users] wang=123456,admin //用户wang,密码123456,拥有admin角色 jack=123,teacher [roles] admin=user:* //admin角色拥有user:*的权限 teacher=student:* [urls] /login=anon //对于/login访问可以匿名进行 /admin=authc //对于/admin访问时必须认证过 |
在webapp下创建页面
- login.jsp
<body> <form action="login" method="post"> userName:<input name="userName" type="text"><br> password:<input name="password" type="password"><br> <input type="submit" value="login"> </form> </body> |
- success.jsp欢迎页面
创建LoginServlet.java
public class LoginServlet extends HttpServlet{
private static final long serialVersionUID = -7255349940789803107L;
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("login doGet"); request.getRequestDispatcher("login.jsp").forward(request, response);
}
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("login Post"); String userName = request.getParameter("userName"); String password = request.getParameter("password"); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(userName, password); try { subject.login(token); //重定向客户端跳转,1.地址栏变化,2.request不传递 response.sendRedirect("success.jsp"); //成功就重定向到success.jsp页面 }catch(Exception e) { e.printStackTrace(); //请求转发:服务器端跳转,1.地址栏url不变,2.request值可以取得 request.getRequestDispatcher("login.jsp").forward(request, response); } } } |
创建AdminServlet.java
public class AdminServlet extends HttpServlet{
private static final long serialVersionUID = -4834638728907791133L;
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("admin doget"); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("admin dopost"); } } |
修改web.xml
<servlet> <servlet-name>loginServlet</servlet-name> <servlet-class>com.kingsoft.servlet.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>loginServlet</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping>
<servlet> <servlet-name>adminServlet</servlet-name> <servlet-class>com.kingsoft.servlet.AdminServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>adminServlet</servlet-name> <url-pattern>/admin</url-pattern> </servlet-mapping> |
测试拦截效果
1. http://localhost:8080/shiro-web/admin
由于/admin=authc //对于/admin访问时必须认证过
所以被转发回了http://localhost:8080/shiro-web/login
2. http://localhost:8080/shiro-web/login
如果输入了用户名wang 密码12345后拥有了admin角色
wang=123456,admin
修改shiro.ini
[main] authc.loginUrl=/login roles.unauthorizedUrl=/none.jsp perms.unauthorizedUrl=/none.jsp [users] wang=123456,admin jack=123,teacher zhangsan=123 [roles] admin=user:* teacher=student:* [urls] /login=anon /admin=authc /student=roles[teacher] |
创建none.jsp用于展示权限不足的页面
当在地址栏中输入:http://localhost:8080/shiro-web/login
并填写用户名:zhangsan 密码 123
时登录成功,但是zhangsan并不具备任何角色
在登录成功页面修改http://localhost:8080/shiro-web/student时显示权限不足,说明配置成功,因为/student必须拥有teacher角色才可以登录
/student=roles[teacher]
这时可以使用jack--123进行登录,才能拥有teacher角色
URL的匹配方式
1. 字符 ? 可以匹配单个字符 /admin?=authc
可以匹配/admin3或/admin5或/adminx 但是不能匹配/admin23或admin
2. 字符 * 可以匹配多个字符 /admin*=authc
可以匹配/adminxyz,/admin234,/admin 但是不能匹配/adminxyxz/xyz
3. 字符**可以匹配多个路径 /admin/**=authc
可以匹配/admin/xyz/zyx这种多路径,但是不能匹配/admin234
4. 字符*/**可以任意匹配路径和多个字符 /admin*/**=authc
可以任意匹配/admin/zbc/xyz, /admin123/abc/def , /admin234 , /admin
Shiro标签
在success.jsp中使用Shiro标签用于展示用户的权限信息
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <shiro:hasRole name="admin"> //admin角色登录显示 欢迎有Admin角色的用户登录 <shiro:principal /> //显示登录人的信息 </shiro:hasRole> <shiro:hasRole name="teacher"> //teacher角色登录显示 欢迎有teacher角色的用户登录 <shiro:principal /> </shiro:hasRole> <shiro:hasPermission name="student:*"> //拥有student:*的权限人登录信息 欢迎有student:*权限的用户登录<shiro:principal /> </shiro:hasPermission> </body> </html> |
Shiro会话机制
一般管理会话,shiro也是调用serlvet容器中来进行,大概了解
修改LoginServlet.java
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("login Post"); String userName = request.getParameter("userName"); String password = request.getParameter("password"); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(userName, password); try { subject.login(token); Session session = subject.getSession(); System.out.println("sessionId:"+session.getId()); System.out.println("host:"+session.getHost()); System.out.println("timeout:"+session.getTimeout()); System.out.println("lastAccessTime:"+session.getLastAccessTime()); session.setAttribute("info", session.getId()); //session传递 response.sendRedirect("success.jsp"); }catch(Exception e) { e.printStackTrace(); request.getRequestDispatcher("login.jsp").forward(request, response); } } |
修改success.jsp
显示传递过来的session值 ${info}<br>
|
自定义的Realm(之前的内容都是把用户的密码,角色,权限写在shiro.ini配置文件中),这次需要把相关的用户,角色,权限放在数据库中
修改之前的工程
在pom.xml中需要添加mysql的连接驱动
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency> |
创建用户表t_user
|
创建角色表t_role
|
创建权限表t_permission
|
创建实体类User.java
public class User {
private Integer id; private String userName; private String password; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } 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; } } |
创建连接数据库的工具类
import java.sql.Connection; import java.sql.DriverManager;
public class DBUtils {
public Connection getCon() throws Exception{ Class.forName("com.mysql.jdbc.Driver"); Connection con=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydata", "root", "123"); return con; }
public void closeCon(Connection con)throws Exception{ if(con!=null){ con.close(); } }
public static void main(String[] args) { DBUtils dbUtil=new DBUtils(); try { dbUtil.getCon(); System.out.println("数据库连接成功"); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); System.out.println("数据库连接失败"); } } } |
创建UserDao.java
public class UserDao {
public User getByUserName(Connection connection, String userName)throws Exception{ User userResult = null; String sql = "select * from t_user where userName = ?"; PreparedStatement pStatement = connection.prepareStatement(sql); pStatement.setString(1, userName); ResultSet rs = pStatement.executeQuery(); if(rs.next()) { userResult = new User(); userResult.setId(rs.getInt("id")); userResult.setUserName(rs.getString("userName")); userResult.setPassword(rs.getString("password")); } return userResult; }
/** * * <b>Description</b><br> * (获取用户角色) * <br> * -------------------------------------------------<br> * <b>A我去 2019年10月21日 下午10:41:48</b> */ public Set<String> getRoles(Connection connection, String userName)throws Exception{ Set<String> roles = new HashSet<String>(); String sql = "SELECT r.roleName FROM t_role r JOIN t_user u ON r.id = u.roleId AND u.userName = ?"; PreparedStatement pStatement = connection.prepareStatement(sql); pStatement.setString(1, userName); ResultSet resultSet = pStatement.executeQuery(); while(resultSet.next()) { roles.add(resultSet.getString("roleName")); } return roles; }
/** * * <b>Description</b><br> * (获取用户的权限) * <br> * -------------------------------------------------<br> * <b>A我去 2019年10月21日 下午10:41:22</b> */ public Set<String> getPermissions(Connection connection, String userName) throws Exception{ Set<String> permissions = new HashSet<String>(); String sql = "SELECT p.perName FROM t_user u JOIN t_permission p ON p.roleId= u.roleId JOIN t_role r ON u.roleId=r.id WHERE u.userName = ?"; PreparedStatement pStatement = connection.prepareStatement(sql); pStatement.setString(1, userName); ResultSet resultSet = pStatement.executeQuery(); while(resultSet.next()) { permissions.add(resultSet.getString("perName")); } return permissions; } } |
创建自定义的MyRealm.java
import java.sql.Connection;
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.kingsoft.dao.UserDao; import com.kingsoft.entity.User; import com.kingsoft.utils.DBUtils;
public class MyRealm extends AuthorizingRealm{
private UserDao userDao = new UserDao(); private DBUtils utils = new DBUtils();
/** * 为登录成功的用户授权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String userName = (String)principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); Connection connection = null; try { connection = utils.getCon(); authorizationInfo.setRoles(userDao.getRoles(connection,userName)); authorizationInfo.setStringPermissions(userDao.getPermissions(connection, userName));
}catch(Exception e) { e.printStackTrace(); }finally { try { utils.closeCon(connection); } catch (Exception e) { e.printStackTrace(); } } return authorizationInfo; }
/** * 验证当前登录的用户 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String userName = (String)token.getPrincipal(); Connection connection = null; try { connection = utils.getCon(); User user = userDao.getByUserName(connection, userName); if(user!=null) { AuthenticationInfo authcInfo = new SimpleAuthenticationInfo( user.getUserName(),user.getPassword(),"aa"); return authcInfo; }else { return null; } }catch(Exception e) { e.printStackTrace(); }finally { try { utils.closeCon(connection); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } return null; } } |
创建LoginServlet.java
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = -7255349940789803107L;
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { System.out.println("login doGet"); request.getRequestDispatcher("login.jsp").forward(request, response);
}
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { System.out.println("login Post"); String userName = request.getParameter("userName"); String password = request.getParameter("password"); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(userName, password); try { subject.login(token); Session session = subject.getSession(); System.out.println("sessionId:" + session.getId()); System.out.println("host:" + session.getHost()); System.out.println("timeout:" + session.getTimeout()); System.out.println("lastAccessTime:" + session.getLastAccessTime()); session.setAttribute("info", session.getId()); response.sendRedirect("success.jsp"); } catch (Exception e) { e.printStackTrace(); request.getRequestDispatcher("login.jsp").forward(request, response); } } } |
加密
在工具包utils下创建EncryptionUtils.java
package com.kingsoft.utils;
import org.apache.shiro.codec.Base64; import org.apache.shiro.crypto.hash.Md5Hash;
public class EncryptionUtils {
/** * * <b>Description</b><br> * (Base64加密) * <br> * -------------------------------------------------<br> * <b>A我去 2019年10月22日 下午9:24:26</b> */ public static String encBase64(String str) { return Base64.encodeToString(str.getBytes()); }
/** * * <b>Description</b><br> * (Base64解密) * <br> * -------------------------------------------------<br> * <b>A我去 2019年10月22日 下午9:26:29</b> */ public static String decBase64(String str) { return Base64.decodeToString(str); }
/** * * <b>Description</b><br> * (MD5加密,salt加盐) * <br> * -------------------------------------------------<br> * <b>A我去 2019年10月22日 下午9:34:49</b> */ public static String encMD5(String source, String salt) { return new Md5Hash(source, salt).toString(); }
/** * * <b>Description</b><br> * (MD5加密,salt加盐,num散列的次数) * <br> * -------------------------------------------------<br> * <b>A我去 2019年10月22日 下午9:37:35</b> */ public static String encMD5(String source, String salt, int num) { return new Md5Hash(source, salt, num).toString(); }
public static void main(String[] args) { String str = "a"; String str1 = "YQ=="; System.out.println("Base64加密:"+EncryptionUtils.encBase64(str)); System.out.println("Base64解密:"+EncryptionUtils.decBase64(str1)); System.out.println("MD5加盐加密:"+EncryptionUtils.encMD5(str, "abc123")); System.out.println("MD5加盐加密64次散列:"+EncryptionUtils.encMD5(str, "abc123", 64)); } } |
以后可以将加密的字符串插入用户表的密码中,当登录时就会比对用户表中的密码,还需修改登录Servlet
修改LoginServlet.java
String userName = request.getParameter("userName"); String password = request.getParameter("password"); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken (userName, EncryptionUtils.encMD5(password, "abc123")); |
一般都会把”abc123”称为盐,一般情况下这个盐放在配置文件中保存
Shiro+Spring整合
创建Maven工程
单击“Finish”完成创建Maven工程
创建出的Maven工程没有src/main/java目录
在工程上单击右键Build Path àConfigure Build Path… 将默认的JRE删除,并单击”Add Library”重新添加JRE运行库
选择JRE System Library 选择默认的工作空间JRE 应用并保存后,可以看到src/main/java目录出现了
接下来我们看默认生成的webapp目录格式也不对 这时我们可以创建一个临时的动态Web工程将对应的目录和web.xml文件拷贝一下 以上操作就可以创建出标准的Maven web工程 |
修改pom.xml文件,引入对应的jar包
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- 添加Servlet支持 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.1</version> </dependency> <!-- 添加jtl支持 --> <dependency> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency>
<!-- 添加Spring支持 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.1.7.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.2.3</version> </dependency> <!-- 添加日志支持 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!-- 添加mybatis支持 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.3.0</version> </dependency> <!-- jdbc驱动包 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.12</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.4</version> </dependency> </dependencies> |
创建包
|
创建实体类
package com.kingsoft.entity;
public class User {
private Integer id; private String userName; private String password; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } 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; } } |
创建UserDao.java
public interface UserDao {
/** * 通过用户名查询用户 */ public User getByUserName(String userName);
/** * 通过用户名查询角色信息 */ public Set<String> getRoles(String userName);
/** * 通过用户名查询权限信息 */ public Set<String> getPermissions(String userName); } |
创建UserMapper.xml
<?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.kingsoft.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.perName 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> |
创建UserService.java接口
public interface UserService {
/** * 通过用户名查询用户 * @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); } |
创建UserServiceImpl.java实现类
@Service("userService") public class UserServiceImpl implements UserService{
@Resource private UserDao userDao;
public User getByUserName(String userName) { return userDao.getByUserName(userName); }
public Set<String> getRoles(String userName) { return userDao.getRoles(userName); }
public Set<String> getPermissions(String userName) { return userDao.getPermissions(userName); } } |
创建UserController.java
@Controller @RequestMapping("/user") public class UserController {
@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"; } } } |
创建MyRealm.java
public class MyRealm extends AuthorizingRealm{
@Resource private UserService userService;
/** * 为当限前登录的用户授予角色和权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String userName=(String)principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo(); authorizationInfo.setRoles(userService.getRoles(userName)); authorizationInfo.setStringPermissions(userService.getPermissions(userName)); return authorizationInfo; }
/** * 验证当前登录的用户 */ @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; }else{ return null; } } } |
创建配置文件在src/main/resources目录下
|
拷贝一份log4j.properties,内容如下
log4j.rootLogger=DEBUG, Console
#Console log4j.appender.Console=org.apache.log4j.ConsoleAppender log4j.appender.Console.layout=org.apache.log4j.PatternLayout log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.logger.java.sql.ResultSet=INFO log4j.logger.org.apache=INFO log4j.logger.java.sql.Connection=DEBUG log4j.logger.java.sql.Statement=DEBUG log4j.logger.java.sql.PreparedStatement=DEBUG |
创建mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 别名 --> <typeAliases> <package name="com.kingsoft.entity"/> </typeAliases> </configuration>
|
创建spring-mvc.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:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<!-- 使用注解的包,包括子集 --> <context:component-scan base-package="com.kingsoft.controller" />
<!-- 视图解析器 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/" /> <property name="suffix" value=".jsp"></property> </bean>
</beans> |
创建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:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<!-- 自动扫描Service --> <context:component-scan base-package="com.kingsoft.service" />
<!-- 配置数据源 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mydata"/> <property name="username" value="root"/> <property name="password" value="123"/> </bean>
<!-- 配置mybatis的sqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 自动扫描mappers.xml文件 --> <property name="mapperLocations" value="classpath:com/kingsoft/mappers/*.xml"> </property> <!-- mybatis配置文件 --> <property name="configLocation" value="classpath:mybatis-config.xml"></property> </bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.kingsoft.dao" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> </bean>
<!-- (事务管理)transaction manager, use JtaTransactionManager for global tx --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
<!-- 自定义Realm --> <bean id="myRealm" class="com.kingsoft.realm.MyRealm"/>
<!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myRealm"/> </bean>
<!-- Shiro过滤器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- Shiro的核心安全接口,这个属性是必须的 --> <property name="securityManager" ref="securityManager"/> <!-- 身份认证失败,则跳转到登录页面的配置 --> <property name="loginUrl" value="/index.jsp"/> <!-- 权限认证失败,则跳转到指定页面 --> <property name="unauthorizedUrl" value="/unauthor.jsp"/> <!-- Shiro连接约束配置,即过滤链的定义 --> <property name="filterChainDefinitions"> <value> /login=anon /admin*=authc /student=roles[teacher] /teacher=perms["user:create"] </value> </property> </bean>
<!-- 保证实现了Shiro内部lifecycle函数的bean执行 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 开启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>
<!-- 配置事务通知属性 --> <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="check*" 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 id="serviceOperation" expression="execution(* com.kingsoft.service.*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation" /> </aop:config>
</beans> |
修改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>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> <!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 --> <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>
<!-- Spring配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- 编码过滤器 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class> org.springframework.web.filter.CharacterEncodingFilter </filter-class> <async-supported>true</async-supported> <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> <!-- Spring监听器 --> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener>
<!-- 添加对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> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
</web-app> |
创建登录和验证成功页面
|
创建index.jsp
<form action="${pageContext.request.contextPath }/user/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> |
创建success.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> ${info } 欢迎你! <shiro:hasRole name="admin"> 欢迎有admin角色的用户!<shiro:principal/> </shiro:hasRole> <shiro:hasPermission name="student:create"> 欢迎有student:create权限的用户!<shiro:principal/> </shiro:hasPermission> </body> </html> |