Shiro
1.Shiro的作用
- Apache Shiro™是一个功能强大且易于使用的Java安全框架,它执行身份验证,授权,加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序-从最小的移动应用程序到最大的Web和企业应用程序。
2.Shiro的体系结构
我们的应用代码直接交互的是Subject,所以说Shiro的对外核心是Subject(主体)
Subject | 主体,基本上表示为当前正在执行的用户,它只是不被称为“用户”,因为“用户”一词通常与人类相关联。与当前应用交互的都是主体,例如爬虫。所有Subject都绑定到SecurityManager,与Subject的所有交互都会委派给SecurityManager,SecurityManager才是真正的执行者,类似于门面模式。 |
---|---|
Shiro SecurityManager | 主体的“幕后”对应对象是SecurityManager。主体代表当前用户的安全操作,而SecurityManager管理所有用户的安全操作。它是Shiro体系结构的核心,它负责与其他组件交互,类似于DispatcherServlet |
Realm | Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源 |
3.Shiro的内部结构
- Subject,主体,当前与软件交互的实体(用户,第三方服务,计划任务等)的特定于安全性的“视图”。
- SecurityManager,安全管理器,Shiro体系结构的核心,用于协调其托管组件以确保它们能够顺利协同工作。
- Authenticator,认证器,最通俗的解释就是认证即登录。
- Authorizer,授权器,我个人觉得,授权就是认证后给用户权限,当然,这个权限我们一般都是从数据库查出来的。
- SessionManager,Shiro有自己特有的session,SessionManager主要负责创建和管理用户session生命周期。
- SessionDAO,SessionDAO执行Session(CRUD)操作`SessionManager。
- CacheManager,缓存管理器,来管理用户,角色,权限的缓存,由于这些数据基本上很少改变,放到缓存中后可以提高访问的性能。
- crypto,用于加密解密。
4.SpringBoot整合Shiro
4.1 引入pom坐标
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
<scope>runtime</scope>
</dependency>
4.2 Shiro配置类
package org.best.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.Map;
/**
* Shiro配置类
*/
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager manager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
//设置securityManager
factoryBean.setSecurityManager(manager);
//设置登录成功页面
factoryBean.setLoginUrl("/index");
//设置未授权页面
factoryBean.setUnauthorizedUrl("/403");
//配置拦截链
/**
* anon:无需认证就可以访问
* authc:认证才可以访问
* user:必须设置 记住我 才能用
* perms:拥有对某个资源的权限才能访问
* role:拥有某个角色权限才能访问
*/
Map<String, String> chainMap = new LinkedHashMap<>();
//无需认证即可登录
chainMap.put("/","anon");
chainMap.put("/login","anon");
//拥有对某个资源的权限可登录
chainMap.put("/user","perms[user]");
chainMap.put("/admin","perms[admin]");
//设置需要权限的页面,参数为map,chain为链的意思,一般情况下有链我们用Linked系列的数据结构
factoryBean.setFilterChainDefinitionMap(chainMap);
return factoryBean;
}
/**
*
* @param realm 自定义realm
* @return 安全管理器
*/
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getRealm") MyRealm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm
securityManager.setRealm(realm);
return securityManager;
}
/**
*
* @return 返回自定义realm
*/
@Bean
public MyRealm getRealm(){
return new MyRealm();
}
}
4.3 自定义Realm
package org.best.config;
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.subject.PrincipalCollection;
import org.best.pojo.User;
import org.best.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 自定义Realm
*/
public class MyRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//将认证中存进去的信息取出来,转换为user
User user = (User) principals.getPrimaryPrincipal();
//获取对象名
String username = user.getUsername();
//根据用户名查询权限并授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission(userService.findUserByName(username).getPermission());
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//将传过来的token进行强制转型
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
//获取User的用户名
String username = usernamePasswordToken.getUsername();
//到数据库根据用户名查询
User user = userService.findUserByName(username);
if (user==null){
return null;
}
/**
* 校验密码
* 第一个参数任意
* 第二个参数为密码
* 第三个参数为当前Realm的名字
*/
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
//获取shiro的session域对象,并将信息存到狱中
SecurityUtils.getSubject().getSession().setAttribute("user",user);
return info;
}
}
4.4 自定义对象
package org.best.pojo;
public class User {
//用户编号
private int id;
//用户名
private String username;
//用户密码
private String password;
//邮箱
private String email;
//权限
private String permission;
//set get tostring 省略
}
4.5 自定义控制层
package org.best.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.best.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class ShiroController {
//设置登录页
@GetMapping(value = {"/","/login"})
public String login(){
return "login";
}
//user页面
@GetMapping("/user")
@ResponseBody
public String user(){
return "当前为user界面";
}
//admin页面
@GetMapping("/admin")
@ResponseBody
public String admin(){
return "当前为admin界面";
}
//403页面
@GetMapping("/403")
public String noType(){
return "403";
}
//登录
@GetMapping("/goLogin")
@RequiresPermissions(value = {"best:lee:admin","best:lee:user"},logical = Logical.OR)
public String goLogin(User user, Model model){
//如果用户为空,返回登录页
if (user==null){
return "login";
}
//将用户名和密码放入token
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
//获得当前主体
Subject subject = SecurityUtils.getSubject();
try {
//调用realm的认证方法
subject.login(token);
return "index";
} catch(IncorrectCredentialsException e){
model.addAttribute("message", "密码错误");
return "login";
} catch (UnknownAccountException e) {
model.addAttribute("message", "用户名不存在");
return "login";
}
}
}
4.6 自定义Service接口
package org.best.service;
import org.best.pojo.User;
public interface UserService {
User findUserByName(String name);
}
4.7 自定义Service实现类
package org.best.service.imp;
import org.best.mapper.UserMapper;
import org.best.pojo.User;
import org.best.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User findUserByName(String name) {
return userMapper.findUserByName(name);
}
}
4.8 自定义Mapper接口
package org.best.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.best.pojo.User;
@Mapper
public interface UserMapper {
User findUserByName(String name);
}
4.9 自定义UserMapper.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">
<!--namespace为所对应的接口-->
<mapper namespace="org.best.mapper.UserMapper">
<select id="findUserByName" resultType="org.best.pojo.User">
select * from user where username=#{username}
</select>
</mapper>
5.前端页面
5.1 登录页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="goLogin" method="get">
<h5 th:text="${message}"></h5>
用户名:<input type="text" name="username"><br>
密 码:<input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
5.2 首页
<!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>
<a th:href="@{/goLogin}" th:if="${session.user==null}">登录</a>
<div shiro:hasPermission="best:lee:user">
<a th:href="@{/user}" >user可点击</a>
</div>
<div shiro:hasPermission="best:lee:admin">
<a th:href="@{/admin}" >admin可点击</a>
</div>
</body>
</html>
5.3 403页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
权限不够
</body>
</html>
6.application.yml
server:
port: 8080
mybatis:
type-aliases-package: org.best.pojo
mapper-locations: classpath:mapper/*.xml
spring:
datasource:
url: jdbc:mysql://localhost:3306/shop
type: com.alibaba.druid.pool.DruidDataSource
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver