shiro(安全)
学习视频链接:小狂神Springboot
每日格言
鸟欲高飞先振翅,人求上进先读书——李苦禅
shiro
阿帕奇的安全框架
Apache Shiro是一个java的安全管理框架,可以用在javaEE环境下,也可以用在javaSE环境下。
此前我们学习了很多有关阿帕奇的东西:maven,tomcat,等等
官方号称十分钟就可以入门,
官网:https://shiro.apache.org/
为什么学他?:
(1)spring security 功能完善,学习成本偏高;
(2)shiro 学习成本低,简单的安全框架,基本功能存在(登录认证,权限认证);
(3)spring mvc interceptor(拦截器) 只能做登录认证,不能做权限认证。
他能做什么?
Authentication:身份认证/登录;
Authorization:授权;
Session Manager:会话管理;
Cryptography:加密;
Web Support:**Web支持,可以非常容易的集成到Web环境;
Caching:缓存;
Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我。
Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro。
shiro架构
Subject:主体;
SecurityManager:相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
Authenticator:认证器;
Authrizer:授权器,;
Realm:可以有1个或多个Realm,是安全实体数据源;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;
SessionManager:**Shiro自己的Session来管理主体与应用之间交互的数据;
SessionDAO:**DAO大家都用过,数据访问对象,用于会话的CRUD;同时SessionDao也可以使用Cache进行缓存以提高性能。
CacheManager:缓存控制器,管理如用户、角色、权限等的缓存
Cryptography:密码模块
helloshiro
导入相关依赖:官方依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<resources>
<resource>
<directory>${basedir}/src/main/webapp</directory>
</resource>
<resource>
<directory>${basedir}/src/main/resources</directory>
</resource>
<resource>
<directory>${basedir}/src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
<include>**/*.yml</include>
</includes>
</resource>
</resources>
修改后的Quickstart类,
最新版本建议将:
FactorySecurityManager factory = new IniSecurityManagerFactory(classpath:shiro.ini);
SecurityManager securityManager = factory.getInstance();
转换位下面这样
DefaultSecurityManager securityManager = new DefaultSecurityManager();
IniRealm iniRealm = new IniRealm(classpath:shiro.ini);
securityManager.setRealm(iniRealm);
下面代码是Quickstart类:
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simple Quickstart application showing how to use Shiro's API.
*
* @since 0.9 RC2
*/
public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
// The easiest way to create a Shiro SecurityManager with configured
// realms, users, roles and permissions is to use the simple INI config.
// We'll do that by using a factory that can ingest a .ini file and
// return a SecurityManager instance:
// Use the shiro.ini file at the root of the classpath
// (file: and url: prefixes load from files and urls respectively):
DefaultSecurityManager securityManager = new DefaultSecurityManager();
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
securityManager.setRealm(iniRealm);
// for this simple example quickstart, make the SecurityManager
// accessible as a JVM singleton. Most applications wouldn't do this
// and instead rely on their container configuration or web.xml for
// webapps. That is outside the scope of this simple quickstart, so
// we'll just do the bare minimum so you can continue to get a feel
// for things.
SecurityUtils.setSecurityManager(securityManager);
// Now that a simple Shiro environment is set up, let's see what you can do:
// get the currently executing user:
Subject currentUser = SecurityUtils.getSubject();
// Do some stuff with a Session (no need for a web or EJB container!!!)
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}
// let's login the current user so we can check against roles and permissions:
if (!currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
//say who they are:
//print their identifying principal (in this case, a username):
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
//test a role:
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
//test a typed permission (not instance-level)
if (currentUser.isPermitted("lightsaber:wield")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
//a (very powerful) Instance Level permission:
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
//all done - log out!
currentUser.logout();
System.exit(0);
}
}
三大对象:
- Subject
- SecurityManage
- realm
shiro整个流程
常用过滤器如下
- anon:无需认证访问
- authc:必须认证了才能访问
- user:记住我开启了,才可以用
- perms:拥有对某个资源的权限才能访问
- role:该资源必须得到角色权限才可以访问
代码实战:
首先是shiroconfig,我们从下往上配置:
- 首先是创建一个realm类
- 之后是创建shiroconfig
- 之后从下往上配置
- 首先是引入realm类
- 配置安全管理器
- 之后设置过滤工厂
package com.hyc.config;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
@Configuration
public class shrioconfig {
// shirofilterfactoryBean
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
/*
* 常用过滤器如下
* anon:无需认证访问
* authc:必须认证了才能访问
* user:记住我开启了,才可以用
* perms:拥有对某个资源的权限才能访问
* */
* */
Map<String,String> filter = new LinkedHashMap();
filter.put("/add","anon");
filter.put("/upd","authc");
bean.setFilterChainDefinitionMap(filter);
bean.setLoginUrl("/tologin");
return bean;
}
// dafultwebSecurityManager
@Bean(name="SecurityManager")
public DefaultWebSecurityManager getdefaultWebSecurityManager(@Qualifier("Userrealm") userrealm userrealm){
DefaultWebSecurityManager SecurityManager = new DefaultWebSecurityManager();
// 关联Userrealm
SecurityManager.setRealm(userrealm);
return SecurityManager;
}
// 创建realm对象,需要自定义类
@Bean
public userrealm Userrealm() {
return new userrealm();
}
}
realm对象需要引用外面的类 userrealm,我们需要继承AuthorizingRealm来获得授权,认证方法
package com.hyc.config;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class userrealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权=========>");
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("认证=========>");
return null;
}
}
用户认证:
我们需要去编写config类,设置权限,什么路径需要什么权限,
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
/*
* 常用过滤器如下
* anon:无需认证访问
* authc:必须认证了才能访问
* user:记住我开启了,才可以用
* perms:拥有对某个资源的权限才能访问
role:该资源必须得到角色权限才可以访问
* */
Map<String,String> filter = new LinkedHashMap();
filter.put("/user/add","perms[user:add]");
filter.put("/user/upd","perms[user:upd]");
bean.setFilterChainDefinitionMap(filter);
bean.setLoginUrl("/tologin");
bean.setUnauthorizedUrl("/unauth");
return bean;
}
之后去realm去认证,认证的信息是我们从数据库user表中查询出来的数据
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("认证=========>");
// 获取当前的用户
Subject subject = SecurityUtils.getSubject();
// 封装用户数据
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
user user = userService.queryUserByName(token.getUsername());
if (user==null){
return null;
}
//认证的时候创建用户登陆的session
Session session = subject.getSession();
//将用户的属性传入到session中
session.setAttribute("loginUser",user);
//如何让我们的user可以全局使用,我们需要设置info中第一个参数为user
return new SimpleAuthenticationInfo(user,user.getPassword() ,"");
}
之后再认证之后获取用户对象授权,什么对象可以访问什么页面
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权=========>");
//授权信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//传递用户信息
Subject subject = SecurityUtils.getSubject();
user currentUser = (user) subject.getPrincipal();
//从数据库中获取授权角色
info.addStringPermission(currentUser.getParms());
info.addRole("user:add");
info.addRole("user:upd");
return info;
}
登陆功能
controller
@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 uae) {//用户名不存在
model.addAttribute("msg","用户名不存在");
return "login";
} catch (IncorrectCredentialsException ice) {//密码不存在
model.addAttribute("msg","密码错误");
return "login";
}
}
这里有一个拓展可以做:就是密码加密处理
我们这里调用的login()方法会走上面我们配置的一系列流程
整合shiro和thymeleaf
需要的命名空间
xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"
要使用整合我们还需要导入整合包依赖
<!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
之后需要去配置类配置一个新的bean
//整合ShiroDialect:用来整合shiro thymeleaf
@Bean
public ShiroDialect getshiroDialect(){
return new ShiroDialect();
}
完成以上步骤就可以在模版引擎上使用shiro了
前端页面内容
<!DOCTYPE html>
<html lang="en"xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"
>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>你好</h1>
<span th:text="${msg}"></span>
<!--从session中判断值-->
<div th:if="${session.get('loginUser')==null}">
<a th:href="@{/tologin}">登录</a>
</div>
<a th:href="@{/logout}">注销</a>
<p th:text="${msg}"></p>
<hr>
<!--通过shiro中的hasPermission方法,判断登录的用户是否有这个权限,有权限才显示-->
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}" >add</a>
</div>
<div shiro:hasPermission="user:upd">
<a th:href="@{/user/upd}">update</a>
</div>
</body>
</html>
源码
配置相关
shiroconfig
package com.hyc.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
@Configuration
public class shrioconfig {
// shirofilterfactoryBean
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
/*
* 常用过滤器如下
* anon:无需认证访问
* authc:必须认证了才能访问
* user:记住我开启了,才可以用
* perms:拥有对某个资源的权限才能访问
* */
Map<String,String> filter = new LinkedHashMap();
filter.put("/user/add","perms[user:add]");
filter.put("/user/upd","perms[user:upd]");
bean.setFilterChainDefinitionMap(filter);
bean.setLoginUrl("/tologin");
bean.setUnauthorizedUrl("/unauth");
return bean;
}
// dafultwebSecurityManager
@Bean(name="SecurityManager")
public DefaultWebSecurityManager getdefaultWebSecurityManager(@Qualifier("Userrealm") userrealm userrealm){
DefaultWebSecurityManager SecurityManager = new DefaultWebSecurityManager();
// 关联Userrealm
SecurityManager.setRealm(userrealm);
return SecurityManager;
}
// 创建realm对象,需要自定义类
@Bean
public userrealm Userrealm() {
return new userrealm();
}
//整合ShiroDialect:用来整合shiro thymeleaf
@Bean
public ShiroDialect getshiroDialect(){
return new ShiroDialect();
}
}
userrealm
package com.hyc.config;
import com.hyc.pojo.user;
import com.hyc.service.userServiceImpl;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
public class userrealm extends AuthorizingRealm {
@Autowired
userServiceImpl userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权=========>");
//授权信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//传递用户信息
Subject subject = SecurityUtils.getSubject();
user currentUser = (user) subject.getPrincipal();
//授权角色
info.addStringPermission(currentUser.getParms());
info.addRole("user:add");
info.addRole("user:upd");
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("认证=========>");
// 获取当前的用户
Subject subject = SecurityUtils.getSubject();
// 封装用户数据
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
user user = userService.queryUserByName(token.getUsername());
if (user==null){
return null;
}
Session session = subject.getSession();
session.setAttribute("loginUser",user);
return new SimpleAuthenticationInfo(user,user.getPassword() ,"");
}
}
控制层
package com.hyc.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.session.ProxiedSession;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@Controller
public class Mycontroller {
@RequestMapping({"/","/index"})
public String toIndex(Model model){
model.addAttribute("msg","hello");
return "index";
}
@RequestMapping("/user/add")
public String add(){
return "user/add";
}
@RequestMapping("/user/upd")
public String upd(){
return "user/upd";
}
@RequestMapping("/tologin")
public String tologin(){
return "login";
}
@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 uae) {//用户名不存在
model.addAttribute("msg","用户名不存在");
return "login";
} catch (IncorrectCredentialsException ice) {//密码不存在
model.addAttribute("msg","密码错误");
return "login";
}
}
@ResponseBody
@RequestMapping("/unauth")
public String unauth(){
return "您没有权限";
}
@RequestMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.removeAttribute("loginUser");
return "index";
}
}
前端页面:
index
<!DOCTYPE html>
<html lang="en"xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"
>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>你好</h1>
<span th:text="${msg}"></span>
<!--从session中判断值-->
<div th:if="${session.get('loginUser')==null}">
<a th:href="@{/tologin}">登录</a>
</div>
<a th:href="@{/logout}">注销</a>
<p th:text="${msg}"></p>
<hr>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}" >add</a>
</div>
<div shiro:hasPermission="user:upd">
<a th:href="@{/user/upd}">update</a>
</div>
</body>
</html>
login
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>登陆</p>
<p th:text="${msg}" style="color: red"></p>
<form th:action="@{/login}">
<p> 用户名:<input type="text" name="username"></p>
<p> 密码:<input type="password" name="password"></p>
<p><input type="submit" value="提交"></p>
</form>
</body>
</html>
安全框架总结:
学习了Springsecurity和shiro之后,总结出一些学习方法
- 安全框架的核心思想都十分相似,授权,认证,防伪等
- 他们通常都有几个对象,如shiro中的subject,securityManager一样,
- 源码的注释写有方法使用的模版,我们可以通过下载源码去查看注释,
- Springsercurity和shiro的区别,两个我个人认为,除了一个基于Spring之外功能上两者几乎一致
- 使用的感受
- Spring Security基于Spring开发,项目中如果使用Spring作为基础,配合Spring Security做权限更加方便,而Shiro需要和Spring进行整合开发
- 感觉shiro没有类似于Spring Security那样的安全防护
- shiro不需要基于任何框架,依赖性低
- 个人认为:配置的麻不麻烦关键在于项目用不用Spring,我看大神们写博客都说shrio配置要更简单一些,但是简单的上手了两个安全框架之后,我觉得使用了Spring的项目上手security要比shiro简单的多,
- 还有个个人感想,帮助文档的阅读能力太重要了,学习和接触新技术在没有教程的情况下,文档的阅读能力决定了你的学习上限(个人中间有一段只照着官方文档学习,十分痛苦)
- 以上就是安全框架简单上手的全内容啦,