Shiro的学习Helloworld
8月 18, 2017 框架相关, 权限管理
- Apache Shiro是Java的一个安全框架
1.1. Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等 - Shiro基本功能
- 1.身份认证
- 2.权限认证
4.1. 核心要素:(资源,)权限,角色,用户 - 3.集成web进行测试
5.0.1. 1.新建一个maven的web项目
5.0.1.1. 1.authc.loginUrl配置身份认证不通过(未登录时)跳转的地址…(loginUrl是authc的一个属性)
5.0.1.2. 2.roles.unauthorizeUrl配置角色认证不通过跳转的地址…(noAuth.jsp页面目前只有一行字)
5.0.1.3. 3.perms.unauthorizeUrl配置权限认证不通过跳转的地址
5.0.1.4. 4.[users]下配置用户身份信息以及用户角色
5.0.1.5. 5.[roles]下配置角色以及角色的控制权限
5.0.1.6. 6.[urls]下配置访问地址所需的权限, 其中值为”anon过滤器”表示地址不需要登录即可访问; “authc过滤器”表示地址登录才能访问
5.0.1.7. 7.值为 roles[admin] 表示 必须有角色为admin的用户才能范围
5.0.1.8. 8.值为 perms[“student:create”] 表示 必须有权限为”student:create”的用户才能范围
5.0.1.9. 9.多个过滤器用”,”隔开 而且相互为”且”的关系(必须同时满足才能访问)
5.0.1.10. 10.地址可以使用?表示匹配单个任意字符(eg: /home?=authc 表示可过滤 /home1; /homef…..)
5.0.1.11. 11.地址可以使用表示匹配任意个任意字符(eg: /home=authc 表示可过滤 /home123; /homeef…..)
5.0.1.12. 12.地址可以使用表示匹配多路径(eg: /home/=authc 表示可过滤 /home/abc; /home/aaa/bbb…..)
5.0.2. 2.编写sevlet代码
5.0.3. 3.启动项目
5.0.3.1. 测试身份认证
5.0.3.2. 测试角色认证
5.0.3.3. 测试权限认证 - —
6.0.1. 自定义Realm
6.0.1.1. 测试开发步骤
6.0.1.2. 1.myRealm指向自定义Realm的位置
6.0.1.3. 2.securityManager.realms指向自定义Realm的引用(表明使用自定义Realms进行安全认证),可以指向多个,用”,”隔开
Apache Shiro是Java的一个安全框架
Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等
图片说明
Shiro基本功能
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web支持,可以非常容易的集成到Web环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
1.身份认证
配置文件:
********************************
[users]
zj2626=123456
ay2626=456789
********************************
package com.em;
import org.apache.shiro.SecurityUtils;
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 Hello {
public static void main(String args[]) {
//初始化SecurityManager工厂 读取配置文件中的用户名密码
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//获取SecurityManager实例
SecurityManager manager = factory.getInstance();
//把SecurityManager实例绑定到SecurityUtils
SecurityUtils.setSecurityManager(manager);
//得到当前执行的用户
Subject subject = SecurityUtils.getSubject();
//创建token令牌, 用户/密码
UsernamePasswordToken token = new UsernamePasswordToken("zj2626", "123456");
try {
//身份认证 登录
subject.login(token);
System.out.println("登录成功");
} catch (Exception e) {
System.out.println("登录失败");
e.printStackTrace();
}
subject.logout();
}
/*
Subject: 认证主体
Principals: 身份: 用户名
Credentials: 凭证: 密码
Realm: 域
1.jdbc realm | 2.jndi realm | 3.text realm
*/
}
从数据库中读取用户名密码 实现登录
1.配置文件: jdbc_realm.ini (代码只需把读取的文件改成此文件即可测试使用)
[main]
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
dataSource=com.mchange.v2.c3p0.ComboPooledDataSource
dataSource.driverClass=com.mysql.jdbc.Driver
dataSource.jdbcUrl=jdbc:mysql://127.0.0.1:3306/test
dataSource.user=root
dataSource.password=123456
jdbcRealm.dataSource=$dataSource
securityManager.realms=$jdbcRealm
2.权限认证
核心要素:(资源,)权限,角色,用户
用户–(分配)–>角色–(拥有)–>权限–(控制)—->资源
用户代表访问系统的用户,即subject。
三种授权方式
- 编程式授权
1.1. 基于角色的访问控制
1.2. 基于权限的访问控制 - 注解式授权
3.JSP标签授权
####步骤1: 封装一个工具类
public class ShiroUtils {
public static Subject login(String conf, String username, String passowrd) {
//初始化SecurityManager工厂 conf是配置文件名称
Factory
//获取SecurityManager实例
SecurityManager manager = factory.getInstance();
//把SecurityManager实例绑定到SecurityUtils
SecurityUtils.setSecurityManager(manager);
//得到当前执行的用户
Subject subject = SecurityUtils.getSubject();
//创建token令牌, 用户/密码
UsernamePasswordToken token = new UsernamePasswordToken(username, passowrd);
try {
//身份认证 登录
subject.login(token);
} catch (Exception e) {
e.printStackTrace();
}
return subject;
}
}
####步骤2: 测试多种访问控制
/*
基于角色的访问控制方式1
配置文件:shiro_role.ini
******************************
[users]
zj2626=123456,role1,role2
py2626=123456,role1
ay2626=456789,role3
******************************
*/
@Test
public void testHas() {
Subject sub = ShiroUtils.login("shiro_config/shiro_role", "zj2626", "123456");
//判断有没有权限 返回布尔 表示验证的成功与否
boolean bool = sub.hasRole("role1");
if (bool) {
System.out.println("HAS");
}
//判断有没有权限,一次多个分别判断 返回布尔数组
boolean[] booleans = sub.hasRoles(Arrays.asList("role1", "role2", "role3"));
int i = 0;
while (booleans[i]) {
i++;
if (booleans.length <= i) {
break;
}
}
//所有的角色都有才返回true
System.out.println(sub.hasAllRoles(Arrays.asList("role1", "role2", "role3")));
//判断有没有权限 没有则抛异常
sub.checkRole("role1");
sub.checkRole("role3");
//判断多个权限 有一个没有就抛异常 (2种参数形式)
sub.checkRoles(Arrays.asList("role1", "role2", "role3"));
sub.checkRoles("role1", "role2", "role3");
//退出登陆
sub.logout();
}
/*
基于权限的访问控制方式(过程同上)
配置文件:shiro_permision.ini
******************************
[users]
zj2626=123456,role1,role2
py2626=123456,role1
ay2626=456789,role3
[roles]
role1=user:select
role2=user:add,user:update,user:delete
******************************
*/
@Test
public void testPermition() {
Subject sub = ShiroUtils.login("shiro_config/shiro_permision", "py2626", "123456");
System.out.println("用户是否有权限 user:select:" + sub.isPermitted("user:select")); //true
System.out.println("用户是否有权限 user:update:" + sub.isPermitted("user:update")); //false
boolean[] booleans = sub.isPermitted("user:add", "user:select");
System.out.println(booleans[0] + "____" + booleans[1]);
System.out.println(sub.isPermittedAll("user:add", "user:select"));
//没有会抛出异常
sub.checkPermission("user:select");
sub.checkPermissions("user:select", "user:update");
sub.logout();
}
3.集成web进行测试
1.新建一个maven的web项目
项目目录文件
web.xml 配置shiro的必须的配置: 监听器,过滤器
<?xml version=”1.0” encoding=”UTF-8”?>
<!--三个servlet配置 /login跳转到登陆页面 /home跳转到主页,即登陆成功页面 /admin用来测试角色和权限-->
<servlet>
<servlet-name>loginServlet</servlet-name>
<servlet-class>com.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>loginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>homeServlet</servlet-name>
<servlet-class>com.servlet.HomeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>homeServlet</servlet-name>
<url-pattern>/home</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>adminServlet</servlet-name>
<servlet-class>com.servlet.AdminServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>adminServlet</servlet-name>
<url-pattern>/admin</url-pattern>
</servlet-mapping>
<!--shiro监听-->
<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>
<init-param>
<param-name>config</param-name>
<param-value>classpath:shiro.ini</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
pom.xml :所需依赖
shiro.ini: 权限配置文件,配置什么用户有什么角色,什么角色有什么权限
[main]
authc.loginUrl=/login
perms.unauthorizedUrl=/noAuth.jsp
roles.unauthorizedUrl=/noAuth.jsp
[users]
zj2626=123456,admin
ay2626=456789,student
[roles]
admin=user:,student:select
student:student:
[urls]
/login=anon
/home=authc
/admin=roles[admin]
1.authc.loginUrl配置身份认证不通过(未登录时)跳转的地址…(loginUrl是authc的一个属性)
2.roles.unauthorizeUrl配置角色认证不通过跳转的地址…(noAuth.jsp页面目前只有一行字)
3.perms.unauthorizeUrl配置权限认证不通过跳转的地址
4.[users]下配置用户身份信息以及用户角色
5.[roles]下配置角色以及角色的控制权限
6.[urls]下配置访问地址所需的权限, 其中值为”anon过滤器”表示地址不需要登录即可访问; “authc过滤器”表示地址登录才能访问
7.值为 roles[admin] 表示 必须有角色为admin的用户才能范围
8.值为 perms[“student:create”] 表示 必须有权限为”student:create”的用户才能范围
9.多个过滤器用”,”隔开 而且相互为”且”的关系(必须同时满足才能访问)
10.地址可以使用?表示匹配单个任意字符(eg: /home?=authc 表示可过滤 /home1; /homef…..)
11.地址可以使用表示匹配任意个任意字符(eg: /home=authc 表示可过滤 /home123; /homeef…..)
12.地址可以使用表示匹配多路径(eg: /home/=authc 表示可过滤 /home/abc; /home/aaa/bbb…..)
2.编写sevlet代码
LoginServlet.java :身份验证地址
package com.servlet;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
Created by zj on 2017/4/10.
/
public class LoginServlet extends HttpServlet {
/*- 跳转登录界面
* - @param req
- @param resp
- @throws ServletException
@throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(“user no login”);
resp.sendRedirect(“login.jsp”);
}/**
- 进行登录
* - @param req
- @param resp
- @throws ServletException
@throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(“登录”);
String userName = req.getParameter(“userName”);
String password = req.getParameter(“password”);Subject subject = SecurityUtils.getSubject();
//创建token令牌, 用户/密码
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
try {//身份认证 登录 subject.login(token); System.out.println("登录成功"); resp.sendRedirect("success.jsp");
} catch (Exception e) {
System.out.println("账号密码不对"); e.printStackTrace(); resp.sendRedirect("login.jsp");
}
}
}
- 跳转登录界面
//**
login.jsp
<%@ page language=”java” pageEncoding=”UTF-8” %>
<%@ taglib uri=”http://java.sun.com/jsp/jstl/core“ prefix=”c” %>
<!DOCTYPE HTML>
Hello World
登录:
HomeServlet.java :登录成功以及退出登录地址
package com.servlet;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
Created by zj on 2017/4/10.
/
public class HomeServlet extends HttpServlet {
/*- 进入主页(登陆成功界面)
* - @param req
- @param resp
- @throws ServletException
@throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(“主页 get”);req.getRequestDispatcher(“success.jsp”).forward(req, resp);
}
/**
- 用来退出登陆
* - @param req
- @param resp
- @throws ServletException
@throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(“主页 post”);System.out.println(“login out”);
//退出登录
Subject subject = SecurityUtils.getSubject();
subject.logout();resp.sendRedirect(“login.jsp”);
}
}
- 进入主页(登陆成功界面)
//**
success.jsp
<%@ page contentType=”text/html;charset=UTF-8” language=”java” %>
Hello World!
成功!!!AdminServlet.java
package com.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
Created by zj on 2017/4/10.
*/
public class AdminServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {System.out.println("ADMIN GET");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("ADMIN POST");
}
}
3.启动项目
测试身份认证
不登录情况下输入地址 http://localhost:8080/home 跳转到 /login地址转向的jsp页面
输入正确的用户名(ay2626)密码 点击登录 成功; 再次输入地址 http://localhost:8080/home 跳转到成功的jsp
点击退出登录 成功; 再次输入地址 http://localhost:8080/home 跳转到 /login地址转向的jsp页面
测试角色认证
登录 :进行其他认证前先进行身份认证 使用ay2626用户登录(其角色只有student) 登录成功 跳转到success.jsp
输入地址http://localhost:8080/admin 跳转到 http://localhost:8080/noAuth.jsp 表示没有权限访问此地址
退出登录 用zj2626再次登录测试 登录成功 再次输入http://localhost:8080/admin 控制台打印”ADMIN POST” 表示访问成功
测试权限认证
可把配置文件中 /admin的过滤器改为 /admin=perms[“student:create”] 进行测试
测试发现 有权限的ay2626用户可以访问而没有权限的zj2626不能访问
—
自定义Realm
实际开发中用户权限的配置要存放在数据库,so需要使用自定义realm来读取数据库中权限配置然后为用户赋予权限
测试开发步骤
1.添加数据库依赖
2.设计构建测试的数据库表:有三个表:分别存储用户,角色,权限 并其他两个都与角色关联, 然后存入部分数据 ,再根据三个表建立对应实体(简单实体)
3.添加数据库操作类(写的并不完善 仅测试使用)
package com.servlet;
import java.sql.*;
/**
Created by zj on 2017/4/11.
*/
public class DBUtil {//获取数据库连接
private static Connection getConnection() {try { Class.forName("com.mysql.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "fangshuoit"); return connection; } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } return null;
}
/**
- 通过用户名获取用户信息
* - @param name
- @return
@throws SQLException
/
public static User getByUserName(String name) throws SQLException {
String sql = “select from ay_user where loginName = ?”;PreparedStatement preparedStatement = getConnection().prepareStatement(sql);
preparedStatement.setString(1, name);ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {return new User(resultSet.getInt("id"), resultSet.getString("loginName"), resultSet.getString("password"));
}
return null;
}/**
- 通过用户名获取用户角色
* - @param name
- @return
@throws SQLException
*/
public static Role getRolesByUserName(String name) throws SQLException {
String sql = “select roleId from ay_user where loginName = ?”;PreparedStatement preparedStatement = getConnection().prepareStatement(sql);
preparedStatement.setString(1, name);ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {Integer id = resultSet.getInt("roleId"); sql = "select * from ay_role where id = ?"; preparedStatement = getConnection().prepareStatement(sql); preparedStatement.setInt(1, id); resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { return new Role(resultSet.getInt("id"), resultSet.getString("name")); }
}
return null;
}/**
- 通过角色id获取角色权限
* - @param roleId
- @return
@throws SQLException
/
public static Perms getPermsByRole(Integer roleId) throws SQLException {
String sql = “select from ay_perms where roleId = ?”;PreparedStatement preparedStatement = getConnection().prepareStatement(sql);
preparedStatement.setInt(1, roleId);ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {return new Perms(resultSet.getInt("id"), resultSet.getString("name"), resultSet.getInt("roleId"));
}
return null;
}
}
3.编写自定义的Realm类 需要AuthorizingRealm类并实现两个方法; 第一个是用来身份验证,第二是用来角色权限验证
- 通过用户名获取用户信息
public class MyRealm extends AuthorizingRealm {
/**
* 验证当前登录的用户(身份认证), 不再需要在配置文件中配置用户的信息和其角色信息
*
* @param token 封装有用户的信息
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName = (String) token.getPrincipal();
System.out.println("要登录的用户 : " + userName);
try {
User user = DBUtil.getByUserName(userName);//这里只是通过用户名验证并获取用户信息,实际开发中需要用户名以及加密的密码
if (user != null) {
return new SimpleAuthenticationInfo(user.getLoginName(), user.getPassword(), "XX");//返回登录信息
} else
return null;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
/**( 身份验证(登录)以后 )
* 为当前用户授予角色和权限(根据登录的用户名,读取数据库中其角色和权限)
*
* @param principals 封装了身份信息
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName = (String) principals.getPrimaryPrincipal();
System.out.println("要权限的用户 : " + userName);
SimpleAuthorizationInfo authenticationInfo = new SimpleAuthorizationInfo();
try {
Role role = DBUtil.getRolesByUserName(userName);
if (null != role) {
Set<String> set = new HashSet<>();
System.out.println("获得的角色: "+ role.getName());
set.add(role.getName());
authenticationInfo.setRoles(set);//赋予角色
Perms perms = DBUtil.getPermsByRole(role.getId());
Set<String> set2 = new HashSet<>();
set2.add(perms.getName());
System.out.println("获得的权限: "+ perms.getName());
authenticationInfo.setStringPermissions(set2);//赋予权限
return authenticationInfo;
}
} catch (SQLException e) {
}
return null;
}
}
4.修改配置文件shiro.ini ,引入自定义Realms,并去掉原来的 [users]和[roles]下的配置
[main]
authc.loginUrl=/login
perms.unauthorizedUrl=/noAuth.jsp
roles.unauthorizedUrl=/noAuth.jsp
myRealm=com.servlet.MyRealm
securityManager.realms=$myRealm
[urls]
/login=anon
/home=authc
/admin=roles[admin]
1.myRealm指向自定义Realm的位置
2.securityManager.realms指向自定义Realm的引用(表明使用自定义Realms进行安全认证),可以指向多个,用”,”隔开
5.启动项目测试 发现:使用zj2626登录可以访问 /admin地址;而ay2626登录不能访问(没有user角色);而且每次请求都会进行认证(控制台打印信息)
Shiro