1. Shiro基础
1.1 Shiro简介
- shiro是一个Java的 安全(权限)框架
- shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。
- shiro可以完成:认证、授权、加密、会话管理,与web集成、缓存
shiro是一个强大而灵活的开源安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密 - 易于理解的Java SecurityApi
- 简单的身份认证,支持多种数据源
- 对角色的简单授权(访问控制),也支持细粒度的鉴权
- 支持一级缓存,以提升应用程序的性能
- 内置的基于POJO企业会话管理,适用于web以及非web的环境
- 异构客户端会话访问
- 非常简单的加密API
- 不跟任何框架或容器捆绑,可以独立运行
1.2 核心组件
1.3 身份认证
- 基本流程
- 流程如下:
1、Shiro把用户的数据封装成标识token,token一般封装着用户名,密码等信息
2、使用Subject门面获取到封装着用户的数据的标识token
3、Subject把标识token交给SecurityManager,在SecurityManager安全中心中,SecurityManager把标识token委托给认证器Authenticator进行身份验证。认证器的作用一般是用来指定如何验证,它规定本次认证用到哪些Realm
4、认证器Authenticator将传入的标识token,与数据源Realm对比,验证token是否合法
1.4 案例演示
1. 使用官方的realm
使用shiro完成一个用户的登录
- 新建项目
- shiro.ini
#声明用户账号
[users]
jay=123
- helloshiro
package org.example;
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;
import org.junit.Test;
/**
* shiro的第一个案例
*/
public class HelloShiro {
@Test
public void shiroLogin(){
//导入ini配置创建工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//工厂构建安全管理器
SecurityManager securitymanager = factory.getInstance();
//使用工具生效安全管理器
SecurityUtils.setSecurityManager(securitymanager);
//使用工具获得subject主体
Subject subject = SecurityUtils.getSubject();
//构建账户密码
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jay", "123");
//打印登录信息
subject.login(usernamePasswordToken);
System.out.println("是否登陆成功:"+subject.isAuthenticated());
}
}
- 结果:
2. 使用自定义的realm
- shiro.ini
#声明自定义的realm,且为安全管理器指定realms
[main]
definitionRealm= com.tjk.cn.realm.DefinitionRealm
securityManager.realms=$definitionRealm
- HelloShiro
public class HelloShiro {
@Test
public void shiroLogin(){
//导入ini配置创建工厂 (在配置文件中找到 规定的realm )
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//工厂构建安全管理器
SecurityManager securitymanager = factory.getInstance();//实例化securitymanager 安全管理器
//使用工具生效安全管理器
SecurityUtils.setSecurityManager(securitymanager);
//使用工具获得subject主体
Subject subject = SecurityUtils.getSubject();
//构建账户密码
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jay", "123");
//打印登录信息
subject.login(usernamePasswordToken); //将登录信息传递给subject 中进行校验
System.out.println("是否登陆成功:"+subject.isAuthenticated()); //返回校验的结果
}
}
- SecurityServiceImpl
/**
* 模拟数据库服务接口的实现
*/
public class SecurityServiceImpl implements SecurityService {
@Override
public String findPasswordByLoginName(String loginName) {
return "123";
}
}
- DefinitionRealm
/**
* 自定义realm
*/
public class DefinitionRealm extends AuthorizingRealm {
/**
* 认证方法
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取登陆令牌(就是登录名)
String username = (String) authenticationToken.getPrincipal();
SecurityServiceImpl securityService = new SecurityServiceImpl();
String password = securityService.findPasswordByLoginName(username);
if(password.equals("")&&password==null){
throw new UnknownAccountException("账户不存在");
}
//返回的是 登录名 密码 和Realm的名字
return new SimpleAuthenticationInfo(username,password,getName());
}
/**
* 鉴权方法
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
shiro的主要运作方式是:subject—>SecurityManager—>Authenticator—>Authentication Strategy–>realm(这个可以自定义)
3 认证源码跟踪
(1)通过debug模式追踪源码subject.login(token) 发现。首先是进入Subject接口的默认实现类。果然,Subject将用户的用户名密码委托给了securityManager去做。
(2)然后,securityManager说:“卧槽,认证器authenticator小弟,听说你的大学学的专业就是认证呀,那么这个认证的任务就交给你咯”。遂将用户的token委托给内部认证组件authenticator去做
(3)事实上,securityManager的内部组件一个比一个懒。内部认证组件authenticator说:“你们传过来的token我需要拿去跟数据源Realm做对比,这样吧,这个光荣的任务就交给Realm你去做吧”。Realm对象:“一群大懒虫!”。
(4)Realm在接到内部认证组件authenticator组件后很伤心,最后对电脑前的你说:“大兄弟,对不住了,你去实现一下呗”。从图中的方法体中可以看到,当前对象是Realm类对象,即将调用的方法是doGetAuthenticationInfo(token)。而这个方法,就是你即将要重写的方法。如果帐号密码通过了,那么返回一个认证成功的info凭证。如果认证失败,抛出一个异常就好了。你说:“什么?最终还是劳资来认证?”没错,就是苦逼的你去实现了,谁叫你是程序猿呢。所以,你不得不查询一下数据库,重写doGetAuthenticationInfo方法,查出来正确的帐号密码,返回一个正确的凭证info
(5)好了,这个时候你自己编写了一个类,继承了AuthorizingRealm,并实现了上述doGetAuthenticationInfo方法。你在doGetAuthenticationInfo中编写了查询数据库的代码,并将数据库中存放的用户名与密码封装成了一个AuthenticationInfo对象返回。可以看到下图中,info这个对象是有值的,说明从数据库中查询出来了正确的帐号密码
(6)那么,接下来就很简单了。把用户输入的帐号密码与刚才你从数据库中查出来的帐号密码对比一下即可。token封装着用户的帐号密码,AuthenticationInfo封装着从数据库中查询出来的帐号密码。再往下追踪一下代码,最终到了下图中的核心区域。如果没有报异常,说明本次登录成功。
1.5 编码、散列算法
1. 编码与解码
Shiro提供了base64和16进制字符串编码/解码的API支持,方便一些编码解码操作。
Shiro内部的一些数据的【存储/表示】都使用了base64和16进制字符串
- 理解base64和16进制字符串编码/解码
- 新建shiro-day01-03encode-decode项目
- EncodesUtil
package com.tjk.cn.tools;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.codec.Hex;
/**
* 编码工具类
*/
public class EncodesUtil {
/**
* HEX string---byte[]
* @param input 输入数组
* @return 字符串
*/
public static String encodeHex(byte[] input){
return Hex.encodeToString(input);
}
/**
* HEX--byte[]---String
* @param input 输入字符串
* @return byte数组
*/
public static byte[] docodeHex(String input){
return Hex.decode(input);
}
/**
* @Description Base64-byte[]--String转换
* @param input 输入数组
* @return String
*/
public static String encodeBase64(byte[] input){
return Base64.encodeToString(input);
}
/**
* @Description Base64-String--byte[]转换
* @param input 输入字符串
* @return byte数组
*/
public static byte[] decodeBase64(String input){
return Base64.decode(input);
}
}
- ClientTest
/**
* @Description 测试16进制编码
*/
@Test
public void testHex(){
String val = "hello";
String flag = EncodesUtil.encodeHex(val.getBytes());//将字符串转为byte[] 进行编码
System.out.println("flag::"+flag);
String valHandler = new String(EncodesUtil.docodeHex(flag));//进行解码
System.out.println("valHandler::"+valHandler);
System.out.println("比较结果:"+val.equals(valHandler));
}
/**
* @Description 测试base64编码
*/
@Test
public void testBase64(){
String val = "holle";
String flag = EncodesUtil.encodeBase64(val.getBytes());//进行编码
String valHandler = new String(EncodesUtil.decodeBase64(flag));//进行解码
System.out.println("比较结果:"+val.equals(valHandler));
}
- shiro目前支持的编码与解码:
base64
(HEX)16进制字符串
2. 散列算法
散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt(盐),比如加密密码“admin”,产生的散列值是“21232f297a57a5a743894a0e4a801fc3”,可以到一些md5解密网站很容易的通过散列值得到密码“admin”,即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,如salt(即盐);这样散列的对象是“密码+salt”,这样生成的散列值相对来说更难破解。
shiro支持的散列算法:
Md2Hash、Md5Hash、Sha1Hash、Sha256Hash、Sha384Hash、Sha512Hash
- DigestsUtil
public class DigestsUtil {
private static final String SHA1="SHA-1";
private static final Integer ITERATIONS=512;
/**
* sha1摘要算法
* @param input 明文字符串 salt 干扰数据
* @param salt
* @return
*/
public static String sha1(String input,String salt){
return new SimpleHash(SHA1,input,salt,ITERATIONS).toString();
}
/**
* 随机生成salt
*@return hex编码的salt
*/
public static String generateSalt(){
SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
return randomNumberGenerator.nextBytes().toHex();
}
public static Map<String,String> entryptPassword(String passwordPlan){
HashMap<String, String> hashMap = new HashMap<>();
String salt = generateSalt();//随机生成salt 也就是干扰素
String password = sha1(passwordPlan, salt); //根据干扰素和原始的密码 生成加密后的密码
hashMap.put("salt",salt); //将salt放入hashmap中 这一步本来可以放入数据库中
hashMap.put("password",password);//将password 放入hashmap中返回
return hashMap;
}
}
- 结果:
1.6 Realm使用散列算法
1. 新建项目
- DefinitionRealm
import com.tjk.cn.service.SecurityService;
import com.tjk.cn.service.impl.SecurityServiceImpl;
import com.tjk.cn.tools.DigestsUtil;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import java.util.Map;
/**
* 生命自定义的reaml
*/
public class DefinitionRealm extends AuthorizingRealm {
/**
*
* @return
* @throws AuthenticationException
*/
public DefinitionRealm() {
//指定密码匹配方式为sha1
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(DigestsUtil.SHA1);
//指定密码迭代次数
matcher.setHashIterations(DigestsUtil.ITERATIONS);
//使用父亲方法使匹配方式生效
setCredentialsMatcher(matcher);
}
/**
* @Description 认证
* @param authenticationToken token对象
* @return
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//从AuthenticationToken中获得登录名称
String loginName = (String) authenticationToken.getPrincipal();
SecurityService securityService = new SecurityServiceImpl();
//相当于是在数据库中找所有的loginName的信息
Map<String, String> map = securityService.findPasswordByLoginName(loginName);
if (map.isEmpty()){
throw new UnknownAccountException("账户不存在");
}
String salt = map.get("salt");
String password = map.get("password");
System.out.println(password);
//传递账号和密码:参数1:缓存对象,参数2:密文密码,参数三:字节salt,参数4:当前DefinitionRealm名称
//这里才是真正的对比
return new SimpleAuthenticationInfo(loginName,password, ByteSource.Util.bytes(salt),getName());
}
/**
* @Description 鉴权
* @param principalCollection 令牌
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
- SecurityServiceImpl
public class SecurityServiceImpl implements SecurityService {
@Override
public Map<String,String> findPasswordByLoginName(String loginName) {
//模拟数据库中存储的密文信息
return DigestsUtil.entryptPassword("123");
}
}
- DigestsUtil
package com.tjk.cn.tools;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;
import java.util.HashMap;
import java.util.Map;
/**
* 摘要
*/
public class DigestsUtil {
public static final String SHA1 = "SHA-1";
public static final Integer ITERATIONS =512;
/**
* @Description sha1方法
* @param input 需要散列字符串
* @param salt 盐字符串
* @return
*/
public static String sha1(String input, String salt) {
return new SimpleHash(SHA1, input, salt,ITERATIONS).toString();
}
/**
* @Description 随机获得salt字符串
* @return
*/
public static String generateSalt(){
SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
return randomNumberGenerator.nextBytes().toHex();
}
/**
* @Description 生成密码字符密文和salt密文
* @param
* @return
*/
public static Map<String,String> entryptPassword(String passwordPlain) {
Map<String,String> map = new HashMap<>();
String salt = generateSalt();//生成一个随机的salt
String password =sha1(passwordPlain,salt);//加密方式 进行加密将原始密码和salt加进去
map.put("salt", salt);//将生成的salt 和密文密码加入map 返回
map.put("password", password);
return map;
}
}
- HelloShiro
public class HelloShiro {
@Test
public void shiroLogin() {
//导入权限ini文件构建权限工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//工厂构建安全管理器
SecurityManager securityManager = factory.getInstance();
//使用SecurityUtils工具生效安全管理器
SecurityUtils.setSecurityManager(securityManager);
//使用SecurityUtils工具获得主体
Subject subject = SecurityUtils.getSubject();
//构建账号token
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jay", "123");
//登录操作
subject.login(usernamePasswordToken);//登录的所有基本信息全部已经封装到了suject中
System.out.println("是否登录成功:" + subject.isAuthenticated());//成功则返回true 失败返回false
}
}
2. 测试
- 真正对比调用的方法:
这里就已经进行了对比 登录信息在开始已经包装成toke传给securityManager了
是在内部进行了对比
不懂可以参考下:
点击查看
想明白就行了 不用细纠
1.7 身份授权
1、首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager。
2、SecurityManager接着会委托给内部组件Authorizer;
3、Authorizer再将其请求委托给我们的Realm去做;Realm才是真正干活的;
4、Realm将用户请求的参数封装成权限对象。再从我们重写的doGetAuthorizationInfo方法中获取从数据库中查询到的权限集合。
5、Realm将用户传入的权限对象,与从数据库中查出来的权限对象,进行一一对比。如果用户传入的权限对象在从数据库中查出来的权限对象中,则返回true,否则返回false。
进行授权操作的前提:用户必须通过认证。
- 案例演示
- 实现doGetAuthorizationInfo方法实现鉴权
- 使用subject类实现权限的校验
- SecurityServiceImpl
package com.tjk.cn.service.impl;
import com.tjk.cn.service.SecurityService;
import com.tjk.cn.tools.DigestsUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class SecurityServiceImpl implements SecurityService {
@Override
public Map<String,String> findPasswordByLoginName(String loginName) {
//模拟数据库中存储的密文信息
return DigestsUtil.entryptPassword("123");
}
/**
* 角色
* @param loginName 用户名
* @return
*/
@Override
public List<String> findRoleByLoginName(String loginName) {
ArrayList<String> list = new ArrayList<>();
list.add("admin");
list.add("dev");
return list;
}
/**
* 资源
* @param loginName 用户名
* @return
*/
@Override
public List<String> findPermissionByLoginName(String loginName) {
List<String> list=new ArrayList<>();
list.add("order:add");
list.add("order:list");
list.add("order:del");
return list;
}
}
上面的模块主要是模拟数据库中的权限和角色查询信息
- DefinitionRealm
/**
* @Description 鉴权
* @param principalCollection 令牌
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
/**
* 拿到用户凭证信息
* 从数据库中查找到对应的角色和权限
* 构建资源校验对象
*/
//1. 拿到用户凭证信息
String loginName = (String)principalCollection.getPrimaryPrincipal();
// System.out.println(loginName);
//2.从数据库中获取对应的角色和权限
SecurityServiceImpl securityService = new SecurityServiceImpl();
List<String> roleByLoginName = securityService.findRoleByLoginName(loginName);//获取到的角色权限
List<String> permissionByLoginName = securityService.findPermissionByLoginName(loginName);//获取到的资源
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(roleByLoginName);//将角色信息交给AuthorizationInfo进行管理
info.addStringPermissions(permissionByLoginName);//将资源信息交给AuthorizationInfo进行管理
return info;
}
info.addRoles(roleByLoginName);//将角色信息交给AuthorizationInfo进行管理
info.addStringPermissions(permissionByLoginName);//将资源信息交给AuthorizationInfo进行管理
- 测试:
@Test
public void testPermissionRealm(){
//首先进行登录认证
Subject subject = shiroLogin();
//判断用户是否登陆成功
System.out.println("是否登录成功:" + subject.isAuthenticated());//成功则返回true 失败返回false
if(subject.isAuthenticated()){
//成功
//校验当前用户是否拥有管理员的角色
boolean admin = subject.hasRole("admin");
System.out.println("是否有管理员角色admin:"+admin);
//校验当前用户没有的角色
try {
subject.checkRole("coder");
System.out.println("当前用户有coder角色");
}catch (Exception e){
System.out.println("当前用户没有coder角色"+e.getMessage());
}
//校验当前用户的权限信息
System.out.println("是否有查看订单的权限"+subject.isPermitted("order:list"));
//校验当前用户没有的权限
try {
subject.checkPermission("order:update");
System.out.println("当前用户有order:update修改的权限");
}catch (Exception e){
System.out.println("当前用户没有order:update没有修改的权限");
}
}else{
System.out.println("登陆失败");
}
}
subject.hasRole(“admin”); 校验当前用户是否拥有管理员的角色
subject.checkRole(“coder”); 校验当前用户没有的角色 如果没有就会报异常
subject.isPermitted(“order:list”) 是否有查看订单的权限
subject.checkPermission(“order:update”); 校验当前用户没有的权限 如果没有就会报异常
- 结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/0b6fb3193f3b4ecd8a021e5c8c2f77d5.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM5NzU5NjY0,size_16,color_
FFFFFF,t_70)
2. web项目集成Shiro
2.1 Web集成原理分析
1. web集成的配置
指定自定义的realm
- #声明自定义的realm,且为安全管理器指定realms
[main]
definitionRealm=com.itheima.shiro.realm.DefinitionRealm
securityManager.realms=$definitionRealm
2. SecurityManager对象创建
- 启动了服务器,监听器捕获到了服务器启动事件。我现在所处的位置EnvironmentLoaderListener监听器的入口处
- 进入方法内查看,它先根据我们的shiroEnvironmentClass变量的值org.apache.shiro.web.env.IniWebEnvironment,初始化一个shiro环境对象
- 最后在创建一个SecurityManager对象,再将其绑定到刚才通过字节码创建的Shiro环境对象中
SecurityManager就完成了初始化
2.2 Shiro默认过滤器
Shiro内置了很多默认的过滤器,比如身份验证、授权等相关的。默认过滤器可以参考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举过滤器
1. 认证相关
2. 授权相关
2.3 Web集成完整案例
1. 新建项目
2. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima.shiro</groupId>
<artifactId>shiro-day01-07web</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>shiro-day01-07web Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- tomcat7插件,命令: mvn tomcat7:run -DskipTests -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<uriEncoding>utf-8</uriEncoding>
<port>8070</port>
<path>/platform</path>
</configuration>
</plugin>
<!-- compiler插件, 设定JDK版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>8</source>
<target>8</target>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
</plugins>
</build>
</project>
3. web.xml
<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_3_0.xsd"
version="3.0">
<display-name>shiro-day01-07web</display-name>
<!--启动时加载shiro监听器-->
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<!--创建权限管理器的上下文-->
<context-param>
<param-name>shiroEnvironmentClass</param-name>
<param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
</context-param>
<!--加载ini文件-->
<context-param>
<param-name>shiroConfigLocations</param-name>
<param-value>classpath:shiro.ini</param-value>
</context-param>
<!--设置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>
</web-app>
4. shiro.ini
#声明自定义的realm,且为安全管理器指定realms
[main]
definitionRealm=com.tjk.cn.realm.DefinitionRealm
securityManager.realms=$definitionRealm
#用户退出后跳转指定JSP页面
logout.redirectUrl=/login.jsp
#若没有登录,则被authc过滤器重定向到login.jsp页面
authc.loginUrl = /login.jsp
[urls]
/login=anon
#发送/home请求需要先登录
/home= authc
#发送/order/list请求需要先登录
/order-list = roles[admin]
#提交代码需要order:add权限
/order-add = perms["order:add"]
#更新代码需要order:del权限
/order-del = perms["order:del"]
#发送退出请求则用退出过滤器
/logout = logout
5.实现层(模拟数据库的操作)
- LoginServiceImpl
package com.tjk.cn.service.impl;
import com.tjk.cn.service.LoginService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
/**
* 登陆服务的实现
*/
public class LoginServiceImpl implements LoginService {
@Override
public boolean login(UsernamePasswordToken token) {
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
}catch (Exception e){
return false;
}
return subject.isAuthenticated();//登录成功返回true 失败返回false
}
@Override
public void logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();//登出
}
}
- SecurityServiceImpl
package com.tjk.cn.service.impl;
import com.tjk.cn.service.SecurityService;
import com.tjk.cn.tools.DigestsUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class SecurityServiceImpl implements SecurityService {
@Override
public Map<String,String> findPasswordByLoginName(String loginName) {
//模拟数据库中存储的密文信息
return DigestsUtil.entryptPassword("123");
}
/**
* 角色
* @param loginName 用户名
* @return
*/
@Override
public List<String> findRoleByLoginName(String loginName) {
ArrayList<String> list = new ArrayList<>();
if("admin".equals(loginName)){
list.add("admin");
}
list.add("dev");
return list;
}
/**
* 资源
* @param loginName 用户名
* @return
*/
@Override
public List<String> findPermissionByLoginName(String loginName) {
List<String> list=new ArrayList<>();
if("jay".equals(loginName)){
list.add("order:add");
list.add("order:list");
list.add("order:del");
}
return list;
}
}
6. 添加web层内容
- HomeServle
@WebServlet(urlPatterns = "/home")
public class HomeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getRequestDispatcher("home.jsp").forward(req,resp);
}
}
- LoginServlet
@WebServlet(urlPatterns = "/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取输入的帐号密码
String username = req.getParameter("loginName");
String password = req.getParameter("password");
//封装用户数据,成为Shiro能认识的token标识
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
LoginService loginService = new LoginServiceImpl();
//将封装用户信息的token进行验证
boolean isLoginSuccess = loginService.login(token);
if (!isLoginSuccess) {
//重定向到未登录成功页面
resp.sendRedirect("login.jsp");
return;
}
req.getRequestDispatcher("/home").forward(req, resp);
}
}
- LogoutServlet
@WebServlet(urlPatterns = "/loginout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
LoginService loginService = new LoginServiceImpl();
loginService.logout();
//ini文件中规定了登出以后会跳转到哪个界面,所以在此不进行规定了
}
}
- OrderAddServlet
@WebServlet(urlPatterns = "/order-add")
public class OrderAddServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getRequestDispatcher("order-add.jsp").forward(req,resp);
}
}
- OrderListServlet
@WebServlet(urlPatterns = "/order-list")
public class OrderListServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getRequestDispatcher("order-list.jsp").forward(req,resp);
}
}
7. 添加JSP
- home.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title></title>
</head>
<body>
<h6>
<a href="${pageContext.request.contextPath}/logout">退出</a>
<a href="${pageContext.request.contextPath}/order-list">列表</a>
<a href="${pageContext.request.contextPath}/order-add">添加</a>
</h6>
</body>
</html>
- login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Title</title>
</head>
<body>
<form method="post" action="${pageContext.request.contextPath}/login">
<table>
<tr>
<th>登陆名称</th>
<td><input type="text" name="loginName"></td>
</tr>
<tr>
<th>密码</th>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="提交"/>
</td>
</tr>
</table>
</form>
</body>
</html>
- oeder-list.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Title</title>
</head>
<body>
添加页面
</body>
</html>
- order-list.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--导入jstl标签库--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>用户列表jsp页面</title>
<style>
table {border:1px solid #000000}
table th{border:1px solid #000000}
table td{border:1px solid #000000}
</style>
</head>
<body>
<table cellpadding="0" cellspacing="0" width="80%">
<tr>
<th>编号</th>
<th>公司名称</th>
<th>信息来源</th>
<th>所属行业</th>
<th>级别</th>
<th>联系地址</th>
<th>联系电话</th>
</tr>
<tr>
<td>1</td>
<td>传智播客</td>
<td>网络营销</td>
<td>互联网</td>
<td>普通客户</td>
<td>津安创意园</td>
<td>0208888887</td>
</tr>
<tr>
<td>2</td>
<td>黑马程序员</td>
<td>j2ee</td>
<td>互联网</td>
<td>VIP客户</td>
<td>津安创意园</td>
<td>0208888887</td>
</tr>
<tr>
<td>3</td>
<td>黑马程序员</td>
<td>大数据</td>
<td>互联网</td>
<td>VIP客户</td>
<td>津安创意园</td>
<td>0208888887</td>
</tr>
</table>
</body>
</html>
8. 测试
访问http://localhost:8080/platform/home
的时候,会被
- 角色过滤
- 资源过滤
2.4 web项目授权
1.基于代码的授权
- 创建项目
- 修改shiro.ini
#声明自定义的realm,且为安全管理器指定realms
[main]
definitionRealm=com.tjk.cn.realm.DefinitionRealm
securityManager.realms=$definitionRealm
#用户退出后跳转指定JSP页面
logout.redirectUrl=/login.jsp
#若没有登录,则被authc过滤器重定向到login.jsp页面
authc.loginUrl = /login.jsp
[urls]
/login=anon
#发送/home请求需要先登录
#/home= authc
#发送/order/list请求需要先登录
#/order-list = roles[admin]
#提交代码需要order:add权限
#/order-add = perms["order:add"]
#更新代码需要order:del权限
#/order-del = perms["order:del"]
#发送退出请求则用退出过滤器
/logout = logout
- HomeServlet
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获得当前的主体:
Subject subject = SecurityUtils.getSubject();
//判断是否登录成功
boolean type = subject.isAuthenticated();
if(type){
req.getRequestDispatcher("home.jsp").forward(req,resp);
}else{
req.getRequestDispatcher("login.jsp").forward(req,resp);
}
}
此时我们通过subject.isAuthenticated()判断是否登录,如果登录则重定向到home.jsp,如果没有登录则转发到/login对应的servlet
- 角色相关
OrderListServlet
修改OrderListServlet的doPost方法,判断是否有admin角色,如果有则转发order-list.jsp,没有则转发/login
@WebServlet(urlPatterns = "/order-list")
public class OrderListServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Subject subject = SecurityUtils.getSubject();
boolean isadmin = subject.hasRole("admin");
if(isadmin){
req.getRequestDispatcher("order-list.jsp").forward(req,resp);
}else{
req.getRequestDispatcher("login.jsp").forward(req,resp);
}
}
}
- 资源相关
OrderAddServlet
@WebServlet(urlPatterns = "/order-add")
public class OrderAddServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Subject subject = SecurityUtils.getSubject();
boolean isadd = subject.isPermitted("order:add");
if(isadd){
req.getRequestDispatcher("order-add.jsp").forward(req,resp);
}else{
req.getRequestDispatcher("login.jsp").forward(req,resp);
}
}
}
因为此时我未登录,也就是说当前没有order:add资源,通过 subject.isPermitted(“order:add”)返回未false
2. 基于jsp的授权方式
需要引入shiro
<%@ taglib prefix=“shiro” uri=“http://shiro.apache.org/tags” %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title></title>
</head>
<body>
<h6>
<a href="${pageContext.request.contextPath}/logout">退出</a>
<shiro:hasRole name="admin"> <%-- 判断角色是否是admin--%>
<a href="${pageContext.request.contextPath}/order-list">列表</a>
</shiro:hasRole>
<shiro:hasPermission name="order:add"><%-- 判断是否有查看add的资源--%>
<a href="${pageContext.request.contextPath}/order-add">添加</a>
</shiro:hasPermission>
</h6>
</body>
</html>