Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和 会话管理等功能。 对于任何一个应用程序,Shiro都可以提供全面的安全管理服务。其不仅可 以用在JavaSE环境,也可以用在JavaEE环境。
Shiro 把 Shiro 开发团队称为“应用程序的四大基石”——身份验证,授权,会话管理和加密作为其目标。
附加功能: 支持和加强在不同环境下所关注的方面
应用程序代码想通过安全管理首先要通过主体(Subject,代表当前用户),主体想通过认证需要调用Shiro安全管理器(Shiro SecurityManager,管理所有主体)。想要进行认证和比对就需要使用Realm(域,提供给你安全数据)获取其中的权限。
总结: 1、应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager; 2、我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。
适用于用户少且不需要在运行时动态常见的情景下使用
在没有使用spring框架之前时,一般会在src目录下创建一个shiro.ini的文件, 对其进行配置 ; 在使用srpingmvc整合后,需要在spring的配置文件中添加spring-shiro.xml(名字可以随便起, 但是要尽量做到见名知意) 1、对象名=全限定类名 相对于调用public无参构造器创建对象 2、对象名.属性名=值 相当于调用setter方法设置常量值 3、对象名.属性名=$对象引用 相当于调用setter方法设置对象引用
详情见: https://jinnianshilongnian.iteye.com/blog/2020820
Subject :主体 Security: 安全 Realm : 域,范围 Authenticator : 认证器 Authentication : 认证 Authorizer : 授权器 Cryptography : 密码,加密 Credential : 证书,凭证 Matcher : 匹配器 Principal : 身份
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- Shiro use SLF4J for logging. We'll use the 'simple' binding in this example app. See http://ww.slf4j.org for more info. -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.5</version>
<scope>test</scope>
</dependency>
<dependencies>
实现最简单的身份认证,后续还会修改
身份就是用来标识主题的属性,唯一即可.可以是身份证号或手机号等 凭证就是只要当前用户(主体)知道的安全知识,如密码或数字签名证书等
所需要的jar
shiro.ini
Shiro.ini
[users]
victor=123456
//实现简单认证
public class AuthenticationTest {
@Test
public void testAuthentication(){
//1.构建SecurityManager工厂
IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2.通过securityManagerFactory工厂获取SecurityManager实例
SecurityManager securityManager = securityManagerFactory.getInstance();
//3.将securityManager设置到运行环境当中
SecurityUtils.setSecurityManager(securityManager);
//4.获取subject实例
Subject subject = SecurityUtils.getSubject();
//5.创建用户名密码验证令牌Token
UsernamePasswordToken token = new UsernamePasswordToken("victor","123456");
//6.进行身份验证
subject.login(token);
//7.判断是否认证通过
System.out.println(subject.isAuthenticated());
}
}
Realm 域,Shiro从从Realm获取安全数据(如用户、角色、权限),可以把Realm看成DataSource,即安全数据源。就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;
jar包
创建安全设置文件 shiro.ini(注意大小写和美元符)
[main]
#配置Realm
jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
#配置数据源
dataSource = com.mchange.v2.c3p0.ComboPooledDataSource
dataSource.driverClass = com.mysql.jdbc.Driver
dataSource.jdbcUrl= jdbc:mysql:///test
dataSource.user = root
dataSource.password = root
jdbcRealm.dataSource = $dataSource
#将Realm注入给securityManager
securityManager.realm = $jdbcRealm
//实现简单认证
public class AuthenticationTest {
@Test
public void testAuthentication(){
//1.构建SecurityManager工厂
IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2.通过securityManagerFactory工厂获取SecurityManager实例
SecurityManager securityManager = securityManagerFactory.getInstance();
//3.将securityManager设置到运行环境当中
SecurityUtils.setSecurityManager(securityManager);
//4.获取subject实例
Subject subject = SecurityUtils.getSubject();
//5.创建用户名密码验证令牌Token
UsernamePasswordToken token = new UsernamePasswordToken("victor","123456");
//6.进行身份验证
subject.login(token);
//7.判断是否认证通过
System.out.println(subject.isAuthenticated());
}
}
0、创建数据库并插入数据
1、导包
2、创建CustomRealm类,继承AuthenticatingRealm, 负责从自定义的数据库中查询账号密码
public class CustomRealm extends AuthenticatingRealm{
private String principal;
private String credentials;
//注意连接源码
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
//jdbc查询操作
//创建jdbc变量
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
//创建变量
try {
//创建连接
conn=DBUtil.getConnection();
//创建sql语句
String sql="select username,passwd from starLogin";
//创建sql预处理命令
ps=conn.prepareStatement(sql);
//给占位符赋值
//执行sql语句
rs=ps.executeQuery();
//遍历
while (rs.next()) {
principal=rs.getString("username");
credentials=rs.getString("passwd");
}
} catch (Exception e) {
e.printStackTrace();
}finally{
//关闭连接
DBUtil.closeAll(rs, ps, conn);
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, credentials, "customRealm");
return simpleAuthenticationInfo;
}
}
注: SimpleAuthenticationInfo构造器中的三个参数的含义
SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)
Principal:身份 (用户名)
credentials:凭证(密码)
realmName:域名称(自定义realm的名称)
3、创建安全设置文件 shiro.ini(注意首字母小写和美元符)
[main]
#配置Realm
customRealm = com.bjsxt.Realms.CustomRealm
#将Realm注入给securityManager
securityManager.realm = $customRealm
4、创建测试类
//实现简单认证
public class AuthenticationTest {
@Test
public void testAuthentication(){
//1.构建SecurityManager工厂
IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2.通过securityManagerFactory工厂获取SecurityManager实例
SecurityManager securityManager = securityManagerFactory.getInstance();
//3.将securityManager设置到运行环境当中
SecurityUtils.setSecurityManager(securityManager);
//4.获取subject实例
Subject subject = SecurityUtils.getSubject();
//5.创建用户名密码验证令牌Token
UsernamePasswordToken token = new UsernamePasswordToken("victor","123");
//6.进行身份验证
subject.login(token);
//7.判断是否认证通过
System.out.println(subject.isAuthenticated());
}
}
对称加密算法 非对称加密算法 散列算符
//MD5加密、加盐以及迭代
public class MD5Test {
@Test
public void testMD5(){
//md5加密
Md5Hash md5 = new Md5Hash("123456");
System.out.println(md5);
//加盐
md5 = new Md5Hash("123456", "bjsxt");
System.out.println(md5);
//迭代
md5 = new Md5Hash("123456", "bjsxt", 2);
System.out.println(md5);
}
}
原理: 给原文加入随机数生成新的MD5值。 使用MD5存在一个问题,相同的password生产的Hash值是相同的,如果两个用户设置了相同的密码,那么数据库当就会存储相同的值,这样是极不安全的。 加Salt可以一定程度上解决这一问题。其基本想法是:当用户首次提供密码时(通常是注册时), 由系统自动往这个密码里撒一些“佐料”,然后再散列。而当用户登录时, 系统为用户提供的代码撒上同样的“佐料”,然后散列,再比较散列值,来确定密码是否正确。
进行加密的次数
//MD5加密、加盐以及迭代
public class MD5Test {
public void testMD5(){
//md5加密
Md5Hash md5 = new Md5Hash("123456");
System.out.println(md5);
//加盐
md5 = new Md5Hash("123456", "bjsxt");
System.out.println(md5);
//迭代
md5 = new Md5Hash("123456", "bjsxt", 2);
System.out.println(md5);
}
}
在Realm接口的实现类AuthenticatingRealm中有credentialsMatcher属性。 意为凭证匹配器。常用来设置加密算法及迭代次数等。
授权,又称作为访问控制,是对资源的访问管理的过程。即对于 认证通过的用户,授予他可以访问某些资源的权限。 授权方式分为: 代码触发、注解触发、标签触发
自定义realm
public class CustomRealm extends AuthorizingRealm {
private String principal;
private String credentials;
private ResultSet rs;
private Statement state;
private Connection conn;
private String roleName;
private String remark;
// 认证方法:获取认证信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 使用JDBC,从数据库获取数据
try {
// 1.注册驱动
Driver driver = new Driver();
DriverManager.registerDriver(driver);
// 2.获取连接对象
String url = "jdbc:mysql:///test";
String user = "root";
String password = "root";
conn = DriverManager.getConnection(url, user, password);
state = conn.createStatement();
// 4.执行sql语句
String sql = "select username,password from users";
rs = state.executeQuery(sql);
// 5.处理结果集
if(rs.first()){
principal = rs.getString("username");
credentials = rs.getString("password");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (state != null) {
try {
state.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, credentials,
"customRealm");
return simpleAuthenticationInfo;
}
// 授权方法:获取授权信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 使用JDBC,从数据库获取数据
try {
// 1.注册驱动
Driver driver = new Driver();
DriverManager.registerDriver(driver);
// 2.获取连接对象
String url = "jdbc:mysql:///test";
String user = "root";
String password = "root";
conn = DriverManager.getConnection(url, user, password);
state = conn.createStatement();
// 4.执行sql语句
//String sql = "select name from role";
String sql = "select remark from permission";
rs = state.executeQuery(sql);
// 5.处理结果集
if(rs.first()){
remark = rs.getString("remark");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (state != null) {
try {
state.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//info.addRole(roleName);
info.addStringPermission(remark);
return info;
}
}
配置文件shiro.ini
[main]
#配置Realm
customRealm = com.bjsxt.realms.CustomRealm
#将Realm注入给SecurityManager
securityManager.realm = $customRealm
//测试类 : 实现简单认证
public class AuthorizationTest {
@Test
public void testAuthorization(){
//1.构建SecurityManager工厂
IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2.通过securityManagerFactory工厂获取SecurityManager实例
SecurityManager securityManager = securityManagerFactory.getInstance();
//3.将securityManager设置到运行环境当中
SecurityUtils.setSecurityManager(securityManager);
//4.获取subject实例
Subject subject = SecurityUtils.getSubject();
//5.创建用户名密码验证令牌Token
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","123456");
//6.进行身份验证
subject.login(token);
//7.判断是否认证通过
System.out.println(subject.isAuthenticated());
//认证通过后进行授权:代码触发
//基于角色授权
//boolean hasRole = subject.hasRole("普通员工");
//System.out.println(hasRole);
//基于权限授权
boolean permitted = subject.isPermitted("一级菜单,基本设置操作权限");
System.out.println(permitted);
}
}
1.导入rbac项目 2.导入shiro相关jar包(shiro-all 以及shiro-spring) 3.在web.xml中添加DelegatingFilterProxy配置 4.编写spring-shiro.xml 5.编写mapper接口及mapper.xml 6.编写service接口及实现类 7.编写controller 8.编写Realm
<!-- 注册DelegatingFilterProxy:通过代理模式将servlet容器中的filter同Spring容器中的bean关联起来 -->
<filter>
<filter-name>shiro</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<!-- 该属性为true表明启用引入filter的init()和destroy(),也就是spring容器中对应的filter生命周期交给servlet容器管理 -->
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<!-- 该属性设置Spring容器中filter的bean的id -->
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiro</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 注册凭证匹配器-->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5"></property>
<property name="hashIterations" value="2"></property>
</bean>
<!-- 注册自定义Realm -->
<bean id="customRealm" class="com.bjsxt.realms.CustomRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"></property>
</bean>
<!-- 注册SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realms" ref="customRealm"></property>
</bean>
<!-- 注册ShiroFilterFactoryBean 注意:id名称必须与web.xml中过滤器名称对应 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"></property>
<property name="loginUrl" value="/user/login.do"></property>
<property name="successUrl" value="/jsp/users.jsp"></property>
<property name="unauthorizedUrl" value="/jsp/refuse.jsp"></property>
<!-- 设置过滤器链属性 -->
<property name="filterChainDefinitions">
<value>
/user/login.do=authc
/**=anon
</value>
</property>
</bean>
</beans>
package com.bjsxt.rbac.mapper;
import com.bjsxt.rbac.pojo.Users;
public interface IUserMapper {
//根据用户名查询用户信息
Users selectByUsername(String username);
}
<?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.bjsxt.rbac.mapper.IUserMapper">
<select id="selectByUsername" resultType="Users">
select
*
from users
where username=#{uname}
</select>
</mapper>
package com.bjsxt.rbac.service;
import com.bjsxt.rbac.pojo.Users;
public interface IUserService {
//根据用户名查询用户信息
Users selectByUsername(String username);
}
```java
package com.bjsxt.rbac.service.impl;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import com.bjsxt.rbac.mapper.IUserMapper;
import com.bjsxt.rbac.pojo.Users;
import com.bjsxt.rbac.service.IUserService;
@Service
public class UserServiceImpl implements IUserService {
@Resource
private IUserMapper mapper;
//根据用户名查询用户信息
@Override
public Users selectByUsername(String username) {
return mapper.selectByUsername(username);
}
}
@Controller
@Scope("prototype")
@RequestMapping("/user")
public class UserController {
/**
* 设定登录失败跳转的资源以及获取异常信息
*/
@RequestMapping("/login.do")
public String login(HttpServletRequest request,Model model){
//查看具体异常信息,获取异常信息名
Object ex = request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
if(UnknownAccountException.class.getName().equals(ex)){
model.addAttribute("msg", "账户不正确");
}else if(IncorrectCredentialsException.class.getName().equals(ex)){
model.addAttribute("msg", "凭证不正确");
}else{
System.out.println(ex);
model.addAttribute("msg", "未知验证异常");
}
return "/jsp/exception.jsp";
}
}
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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import com.bjsxt.rbac.pojo.Users;
import com.bjsxt.rbac.service.IUserService;
public class CustomRealm extends AuthorizingRealm {
@Autowired
private IUserService userService;
// 认证方法:获取认证信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
try {
//调用service
//根据表单传过来的用户名查询用户信息
Users user = userService.selectByUsername(token.getPrincipal().toString());
System.out.println(user);
if (user != null) {
ByteSource newSalt = ByteSource.Util.bytes(user.getPassword_salt());
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(token.getPrincipal(),
user.getPassword(), newSalt, token.getPrincipal().toString());
return simpleAuthenticationInfo;
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
// 授权方法:获取授权信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
}
用于登陆的jsp页面,添加一个单选框
<input type="checkbox" name="rememberMe" value="true"/>记住我<br>
spring-shiro.xml
(标注rem的都是需要添加的)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 注册凭证匹配器-->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5"></property>
<property name="hashIterations" value="2"></property>
</bean>
<!-- 注册自定义Realm -->
<bean id="customRealm" class="com.bjsxt.realms.CustomRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"></property>
</bean>
<!-- 注册SessionManager -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="globalSessionTimeout" value="300000"></property>
<property name="deleteInvalidSessions" value="true"></property>
</bean>
<!-- rem: 注册SimpleCookie -->
<bean id="simpleCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- 设置Cookie名称 -->
<property name="name" value="rememberMe"></property>
<!-- 设置Cookie有效时间 单位为秒-->
<property name="maxAge" value="604800"></property>
</bean>
<!-- rem: 注册RememberMeManager -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie" ref="simpleCookie"></property>
</bean>
<!-- 注册SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realms" ref="customRealm"></property>
<property name="sessionManager" ref="sessionManager"></property>
<property name="rememberMeManager" ref="rememberMeManager"></property>
</bean>
<!-- 注册ShiroFilterFactoryBean 注意:id名称必须与web.xml中过滤器名称对应 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"></property>
<property name="loginUrl" value="/user/login.do"></property>
<property name="successUrl" value="/user/loginSuccess.do"></property>
<property name="unauthorizedUrl" value="/WEB-INF/jsp/refuse.jsp"></property>
<!--rem(/user/loginSuccess.do=user
/menu/showMenu.do=user) : 设置过滤器链属性 -->
<property name="filterChainDefinitions">
<value>
/user/login.do=authc
/**=anon
/user/loginSuccess.do=user
/menu/showMenu.do=user
</value>
</property>
</bean>
</beans>
web.xml中注册
<!-- 注册DelegatingFilterProxy:通过代理模式将servlet容器中的filter同Spring容器中的bean关联起来 -->
<filter>
<filter-name>shiro</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<!-- 该属性为true表明启用引入filter的init()和destroy(),也就是spring容器中对应的filter生命周期交给servlet容器管理 -->
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<!-- 该属性设置Spring容器中filter的bean的id -->
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiro</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>