shiro框架
1.简介
Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。
Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。
Shiro能到底能做些什么呢?
- 验证用户身份
- 用户访问权限控制,比如:1、判断用户是否分配了一定的安全角色。2、判断用户是否被授予完成某个操作的权限
- 在非 Web 或 EJB 容器的环境下可以任意使用Session API
- 可以响应认证、访问控制,或者 Session 生命周期中发生的事件
- 可将一个或以上用户安全数据源数据组合成一个复合的用户 “view”(视图)
- 支持单点登录(SSO)功能
- 支持提供“Remember Me”服务,获取用户关联信息而无需登录
为什么是 Shiro
使用 Shiro 官方给了许多令人信服的原因,因为 Shiro 具有以下几个特点:
- 易于使用——易用性是项目的最终目标。应用程序安全非常令人困惑和沮丧,被认为是“不可避免的灾难”。如果你让它简化到新手都可以使用它,它就将不再是一种痛苦了。
- 全面——没有其他安全框架的宽度范围可以同Apache Shiro一样,它可以成为你的“一站式”为您的安全需求提供保障。
- 灵活——Apache Shiro可以在任何应用程序环境中工作。虽然在网络工作、EJB和IoC环境中可能并不需要它。但Shiro的授权也没有任何规范,甚至没有许多依赖关系。
- Web支持——Apache Shiro拥有令人兴奋的web应用程序支持,允许您基于应用程序的url创建灵活的安全策略和网络协议(例如REST),同时还提供一组JSP库控制页面输出。
- 低耦合——Shiro干净的API和设计模式使它容易与许多其他框架和应用程序集成。你会看到Shiro无缝地集成Spring这样的框架, 以及Grails, Wicket, Tapestry, Mule, Apache Camel, Vaadin…等。
- 被广泛支持——Apache Shiro是Apache软件基金会的一部分。项目开发和用户组都有友好的网民愿意帮助。这样的商业公司如果需要Katasoft还提供专业的支持和服务。
基本功能
2.基本使用
环境准备
shiro不依赖容器,直接创建maven工程即可
pom.xml依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.10.1</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
创建ini文件
shiro获取权限信息可以通过数据库获取,也可以通过ini配置文件获取
[users]
zhangsan=z3
lisi=l4
登入认证概念
-
身份验证:一般需要提供身份id等一些标识信息来表明登录者的身份,如提供email,用户名/密码来证明
-
再shiro中,用户选哟提供principals(身份)和credentials(证明)给shiro,从而应用能验证用户身份
-
principals:身份,即主题的标识属性,可以是任何属性,如果用户,邮箱等,唯一即可,一个主题可有多个principals,但只有一个primary principals,一般是用户名/邮箱/手机号
-
credentials:证明,凭证,即只有主体才知道的安全值,如密码/数字证书等
- 最常见的principals和credentials组合就是用户名/密码
登录认证基本流程
-
收集用户身份/凭证,即如用户名/密码
-
调用subject.login进行登录,如果失败将得到相应的
AuthenticationException
异常,根据异常提示用户错误信息,否则登录成功 -
创建自定义
Realm
的类,继承org.apche.shiro.realm.AuthenticatingRealm
类,实现doGetAuthenticationInfo()
方法
基础代码
public static void main(String[] args) {
//1.初始化获取SecurityManager
BasicIniEnvironment environment = new BasicIniEnvironment("classpath:shiro.ini");
SecurityManager securityManager = environment.getSecurityManager();
SecurityUtils.setSecurityManager(securityManager);
//2获取Subject对象
Subject subject = SecurityUtils.getSubject();
//3创建token对象
AuthenticationToken token = new UsernamePasswordToken("zhangsan", "z3");
try {
//4完成登录
subject.login(token);
System.out.println(token.toString());
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误");
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户错误");
}
}
授权
授权概念
- 授权,也叫访问控制,即在应用中控制谁访问哪些资源
- 主体(Subject),访问应用的用户,再shiro中使用Subject代表该用户,用户只有授权后才能访问相应的资源
- 资源(Resource),再应用中用户可以访问的URL
- 权限(Premission),安全策略中的原子授权单位,通过权限我们可以表示再应用中用户有没有操作某个资源的权力
- shiro支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的)
- 角色(Role),权限的集合,一般情况下会赋予用户角色而不是权限,不同角色拥有一组不同的权限
授权方式
- 编程式:通过if/else代码块完成
- 注解式:通过再执行的方法上放置相应的注解完成,没有权限将抛出相应的异常
- JSP/GSP 标签:在 JSP/GSP 页面通过相应的标签完成
授权
在 ini 配置文件配置用户拥有的角色
[users]
zhangsan=z3,role1,role2
lisi=l4
hasRole/hasRoles;
用于判断用户是否拥有某个角色/某些权限
boolean role = subject.hasRole("role1");
subject.hasRoles(Arrays.asList("role1", "role2"));
在 ini 配置文件配置用户拥有的角色及角色-权限关系
[users]
zhangsan=z3,role1,role2
lisi=l4
[roles]
role1=user:create,user:update
role2=user:create,user:delete
Shiro 提供了 isPermitted 和 isPermittedAll 用于判断用户是否拥有某个权限或所有权限
System.out.println(subject.isPermitted("user:create"));
shiro加密
实际系统开发中,一些敏感信息需要进行加密,比如用户的密码,shiro内嵌很多常用的加密算法,如果MD5加密,shiro可以很简单的使用信息加密。
public static void main(String[] args) {
String secret = "x11";
Md5Hash md5Hash = new Md5Hash(secret);
System.out.println(md5Hash);
// 使用盐值加密
Md5Hash md5Hash1 = new Md5Hash(secret, "java");
// salt + 加密次数
Md5Hash md5Hash2 = new Md5Hash(secret, "salt", 3);
System.out.println(md5Hash2);
System.out.println(md5Hash1);
}
public class Md5Hash extends SimpleHash
public static final String ALGORITHM_NAME = "MD5";
public Md5Hash() {
super("MD5");
}
public Md5Hash(Object source) {
super("MD5", source);
}
public Md5Hash(Object source, Object salt) {
super("MD5", source, salt);
}
public Md5Hash(Object source, Object salt, int hashIterations) {
super("MD5", source, salt, hashIterations);
}
Md5hash
是通过super,将参数传递给父类SimpleHash
,所有也可以直接通过 SimpleHash
指定加密方法
SimpleHash simpleHash = new SimpleHash("Md5", secret, "java", 3);
3.springboot整合shiro
依赖
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.10.0</version>
</dependency>
自定义Realm类
继承AuthorizingRealm
类,重写doGetAuthorizationInfo
和doGetAuthenticationInfo
方法
public class UserRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username = "xin";
String password = "12345";
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
if (!token.getUsername().equals(username)) {
return null;
}
// 密码认证
return new SimpleAuthenticationInfo("", password, "");
}
}
创建配置类
@Configuration
public class ShiroConfig {
// 创建ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier(value = "securityManager") DefaultSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put("/user/update", "authc");
bean.setFilterChainDefinitionMap(map);
return bean;
}
// 创建DefaultSecurityManager
@Bean("securityManager")
public DefaultSecurityManager getDefaultSecurityManager(@Qualifier(value = "userRealm") UserRealm userRealm) {
// DefaultSecurityManager securityManager = new DefaultSecurityManager();
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
// 创建realm对象
@Bean("userRealm")
public UserRealm userRealm() {
return new UserRealm();
}
}
setLoginUrl(String loginUrl)`设置登录页
setFilterChainDefinitionMap
传入map集合过滤需要验证的页面
创建DefaultSecurityManager,使用的类是 DefaultWebSecurityManager
控制器登入方法
@RequestMapping("/login")
public String login(String username, String password, Model model) {
// 获取当前用户
Subject subject = SecurityUtils.getSubject();
// 封装用户数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 执行登入方法,有异常抛出异常
try {
subject.login(token);
return "index";
} catch (UnknownAccountException e) {
model.addAttribute("msg", "用户信息错误");
return "login";
} catch (IncorrectCredentialsException e) {
model.addAttribute("msg", "密码错误");
return "login";
}
}
subject.login()
会调用自定义Realm中的 doGetAuthenticationInfo方法
测试
拦截成功,无设置登入界面
没有得到授权
整合mybatis
验证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
User user = userMapper.getOne(token.getUsername(), String.valueOf(token.getPassword()));
if (user == null) {
return null;
}
return new SimpleAuthenticationInfo("", user.getPassword(), "");
}
授权
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("user:add");
return info;
}
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserMapper userMapper;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("user:add");
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal();
info.addStringPermission(currentUser.getPerms);
return info;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
User user = userMapper.getOne(token.getUsername(), String.valueOf(token.getPassword()));
if (user == null) {
return null;
}
return new SimpleAuthenticationInfo(user, user.getPassword(), "");
}
}