为了方便理解以及使用Shiro,这里先贴上一份官方10分钟快速入门的QuickStart Java入门案例:
/**
* 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) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
// get the currently executing user:
//获取当前用户对象subject
Subject currentUser = SecurityUtils.getSubject();
// 通过当前用户获取session
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 + "]");
}
//判断当前用户是否以认证
if (!currentUser.isAuthenticated()) {
//token令牌
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);
}
}
接下来开始我们的整合,我这里的整合用到Druid连接池,Lombok,thymeleaf模板引擎,Mybatis,web相关jar等。
第一步、依然还是通过maven导入相关的Jar包依赖:
<!--导入shiro和spring整合包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.6.0</version>
</dependency>
<!--导入web包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--thymeleaf模板引擎引入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--导入mybatis整合包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--导入mysql链接器-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--导入druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.24</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--导入lombok,非必要-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- shito整个thymeleaf -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
第二步、搭建相关环境–后端
我的文件目录如下:
shiroConfig配置类:
@Configuration
public class shiroConfig{
/**
*shiro三大核心
*subject 用户
*SecurityManager 管理所有用户
*Realm 连接数据
*
**/
//通过shiroFilterFactoryBean设置安全管理器---对应subject
@Bean
public ShiroFilterFactoryBean getBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器:第三步
bean.setSecurityManager(defaultWebSecurityManager);
/**
* 添加shiro内置过滤器
*anon: 无需认证都可访问
*authc:必须认证了才能访问
*user: 必须拥有 记住我 功能才可以使用
* perms: 拥有对某个资源有权限才能访问
* role: 拥有某个角色权限才能访问
*
* */
//拦截
Map<String, String> filterMap = new LinkedHashMap<>();
/**
* filterMap.put("资源路径","权限")
* */
//设置权限
filterMap.put("/user/add","perms[user:add]");//只有当是user用户,且有add权限才能访问
filterMap.put("/user/update","perms[user:update]");
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
//设置没权限时候的登录页,设定默认登录页
bean.setLoginUrl("/toLogin");
//设置权限不足页面
bean.setUnauthorizedUrl("/Unauthorized");
return bean;
}
@Bean
//DefaultWebSecurityManager---securityManager:第二步
public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联userRealm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建Relam对象,需要自定义类:第一步
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
//整合shiro 和 thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
Subject:Application Code(应用程序代码)和Shiro直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject,Subject代表了当前用户,这个用户不一定是一个具体的人,与当前应用程序交互的任何东西都是Subject,如网络爬虫和机器人等;另外,Subject实际上所有的操作并不是由自己的来完成的,而是委托给SecurityManager来完成,Subject其实更像是一个门面,SecurityManager才是实际的执行者。
Shiro SecurityManager:安全管理器,是Shiro的核心。即所有与安全有关的操作都有SecurityManager来完成。它管理着所有的Subject,并负责与Shiro其他组件进行交互,它相当于SpringMVC中DispatcherServlet的角色;
Realm:Shiro从Realm获取安全数据,即在Shiro中专门用于访问安全方面的数据(如用户、角色和权限等信息),也就是说SecurityManager想要验证用户身份,需要从Realm中获取用户的相应角色/权限信息来进行校验。它类似于Shiro中的Dao,一个为我们提供访问安全数据的接口。
yml配置文件:
spring:
application:
name: shiro-springboot
datasource:
url: jdbc:mysql://localhost:3306/shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username:
password:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource #修改默认hikari配置源
#SpringBoot默认是不注入这些的,需要自己绑定
#druid数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许报错,java.lang.ClassNotFoundException: org.apache.Log4j.Properity
#则导入log4j 依赖就行
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionoProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
# 应用服务 WEB 访问端口
server:
port: 8080
#mybatis别名扫描以及mapper文件下四奥妙
mybatis:
type-aliases-package: com.gdpu.pojo
mapper-locations: classpath:mapper/*.xml
自定义userRealm类:
//自定义userRealM只需要继承AuthorizingRealm即可
public class UserRealm extends AuthorizingRealm {
@Autowired
userServiceImpl userService;
//授权操作
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权操作");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//info.addStringPermission("user:add");
//拿到当前登陆对象
Subject subject = SecurityUtils.getSubject();
user currentUser = (user)subject.getPrincipal();//拿到下文放入principle的user对象
//根据数据库表里面的用户权限设置当前用户权限
info.addStringPermission(currentUser.getPerms());
return info;
}
//认证操作
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了认证操作");
//拿到Controller封装的token对象
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
//连接数据库
user user = userService.queryUserByUserName(userToken.getUsername());
if(user==null){
return null;//UnknownAccountException异常,没有该用户
}
//密码认证,shiro来做,第一个参数可以把从数据库返回的user存到principle里,方便上面授权通过getPrincipal()拿到该对象进行授权
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
}
Controller:
@Controller
public class MyController {
@RequestMapping({"/","/index"})
public String toIndex(Model model){
model.addAttribute("msg","helloShiro");
return "index";
}
@RequestMapping("/user/add")
public String toAdd(){
return "user/add";
}
@RequestMapping("/user/update")
public String toUpdate(){
return "user/update";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
//封装user的token令牌
@RequestMapping("/login")
public String login(String username,String password,String remember,Model model){
//获取subject对象
Subject subject = SecurityUtils.getSubject();
//验证是否已经登陆,如果没登陆则封装令牌
if(!subject.isAuthenticated()){
//封装令牌token
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//判断是否实现记住我
if(remember!=null){
token.setRememberMe(true);
}
//校验登陆,这里的校验规则走userRelam里面的认证规则
try {
//验证成功跳转首页
subject.login(token);
return "index";
}//捕获用户名不存在的异常并将信息反馈到前端
catch (UnknownAccountException uae){
model.addAttribute("msg","用户名不存在");
return "login";
}//捕获密码错误异常并将信息反馈到前端
catch (IncorrectCredentialsException ice){
model.addAttribute("msg","密码错误");
return "login";
}
}
else{
return "toLogin";
}
}
//注销功能
@RequestMapping("/logout")
public String logOut(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/toLogin";
}
//自定义未经授权页面
@RequestMapping("/Unauthorized")
public String toUnauthorized(){
return "Unauthorized";
}
}
mapper:
@Repository
public interface userMapper {
public user queryUserByUserName(String name);
}
pojo:
//lombok自动生成有参无参get,set方法
@NoArgsConstructor
@Data
@AllArgsConstructor
public class user implements Serializable {
private Integer id;
private String username;
private String password;
private String perms;
}
service:
public interface userService {
public user queryUserByUserName(String name);
}
serviceImpl:
@Service
public class userServiceImpl implements userService {
@Autowired
userMapper userMapper;
@Override
public user queryUserByUserName(String name) {
return userMapper.queryUserByUserName(name);
}
}
mapper.xml:
<?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.gdpu.mapper.userMapper">
<select id="queryUserByUserName" resultType="user" parameterType="String" useCache="true">
select * from user where username=#{username}
</select>
</mapper>
第三步、搭建相关测试环境–前端
index.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
<!--引入shiro命名空间以及thymleaf命名空间-->
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
>
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<p th:text="${msg}">
</p>
<h3>首页</h3>
<!--判断是否已经认证授权,未授权显示登陆按钮-->
<div shiro:notAuthenticated>
<a href="/toLogin">登陆</a>
</div>
<!--判断是否已经认证授权,授权显示登陆注销按钮-->
<div shiro:authenticated>
<a href="/logout">注销</a>
</div>
<hr>
<!--有权限才显示,没权限则不显示-->
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">update</a>
</div>
</body>
</html>
login.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--接收后端传过来的信息提示-->
<p th:text="${msg}" style="color: red"></p>
<form th:action="@{/login}">
<input type="text" name="username">用户名<br>
<input type="password" name="password">密码<br>
<input type="checkbox" name="remember">记住我<br>
<input type="submit" value="提交">
</form>
</body>
</html>
效果展示
点击登录后,由于没有身份认证,跳转到login页面进行登陆验证
登陆后,由于Li这个用户只有add权限,因此他只能看到add的操作
拥有update权限的用户登陆所看到的界面
点击记住我的功能
通过F12开发者模式,我们可以看到记住我的功能已经实现
至此,关于shiro的基本核心功能展示就完成了