一、Apache Shiro 介绍及概念
概念:Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
Shiro 框架图:
框架图解析:
Shiro 概念层 架构:
二、Apache Shiro 部分功能讲解:
1、Apache Shiro授权讲解
- 首先调用Subject 进行处理,再委托给 Security Manageer ,Security Manageer会委托给 Authorizer ,Authorizer是真正的授权者。
Shiro 的授权主要包含四个核心:主体、资源、权限、角色
2、 Apache Shiro权限拦截框架图:
3、Apache Shiro会话管理框架图
三、基于SpringBoot的Apache Shiro环境快速搭建与配置
1、环境搭建及使用
主要涉及两点:
(1)创建springboot 工程 Spring-boot_Shiro ,整合 Shiro 依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.20</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
(2)创建数据库表结构 : user 、role、permission、permission_role、user_role、五张表;
CREATE TABLE `user` (
`uid` int(11) NOT NULL AUTO_INCREMENT ,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' ,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' ,
PRIMARY KEY (`uid`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=3
ROW_FORMAT=DYNAMIC;
CREATE TABLE `role` (
`rid` int(11) NOT NULL AUTO_INCREMENT ,
`rname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' ,
PRIMARY KEY (`rid`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=3
ROW_FORMAT=DYNAMIC;
CREATE TABLE `permission` (
`pid` int(11) NOT NULL AUTO_INCREMENT ,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' ,
`url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' ,
PRIMARY KEY (`pid`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=1
ROW_FORMAT=DYNAMIC;
CREATE TABLE `permission_role` (
`rid` int(11) NOT NULL ,
`pid` int(11) NOT NULL ,
INDEX `idx_rid` (`rid`) USING BTREE ,
INDEX `idx_pid` (`pid`) USING BTREE
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=DYNAMIC;
CREATE TABLE `user_role` (
`uid` int(11) NOT NULL ,
`rid` int(11) NOT NULL ,
INDEX `idx_uid` (`uid`) USING BTREE ,
INDEX `idx_pid` (`rid`) USING BTREE
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=DYNAMIC;
(3)创建 user、role、permission 三个类
public class User {
private String username;
private Integer uid;
private String password;
private Set<Role> roles = new HashSet<>();
。。。。。。。。。。。。
}
public class Role {
private Integer rid;
private String rname;
private Set<Permission> permissionSet = new HashSet<>();
...................
}
public class Permission {
private Integer pid;
private String name;
private String url;
。。。。。。。。。。。。。。
}
(4)创建service业务层、mapper 持久层
service 、serviceimpl业务接口及实现类
public interface UserService {
User findByUsername(String username);
}
@Service
@Transactional(readOnly=true)
public class UserServiceimpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User findByUsername(String username) {
return userMapper.findByUsername(username);
}
}
UserMapper.java 持久层
@Component
@Mapper
public interface UserMapper {
User findByUsername(@Param("username") String username);
List<User> getAll();
}
UserMapper.xml
<mapper namespace="com.webcode.springboot.mappers.UserMapper">
<resultMap id="userMap" type="com.webcode.springboot.entities.User">
<id property="uid" column="uid"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<collection property="roles" ofType="com.webcode.springboot.entities.Role">
<id property="rid" column="rid"/>
<result property="rname" column="rname"/>
<collection property="permissionSet" ofType="com.webcode.springboot.entities.Permission">
<id property="pid" column="pid"/>
<result property="name" column="name"/>
<result property="url" column="url"/>
</collection>
</collection>
</resultMap>
<select id="findByUsername" parameterType="string" resultMap="userMap">
SELECT u.*,r.*,p.*
from user u
INNER JOIN user_role ur on ur.uid = u.uid
INNER JOIN role r on r.rid = ur.rid
INNER JOIN permission_role pr on pr.rid = r.rid
INNER JOIN permission p on pr.pid = p.pid
WHERE u.username = #{username}
</select>
<select id="getAll"
resultMap="userMap">
select *
from USER
</select>
</mapper>
(5)配置mapper.xml 包扫描位置及对应的javabean 所在包,用于mapper.xml的映射
application.yml 配置文件:
Bootstrap 启动类:
2、基于Apache Shiro权限管理Case -- 认证授权配置
public class AuthRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 授权要先从 session 取出对象来
User user = (User) principals.fromRealm(this.getClass().getName()).iterator().next();
List<String> permissionList = new ArrayList<>();
Set<Role> roleSet = user.getRoles();
if (CollectionUtils.isNotEmpty(roleSet)){
for (Role role : roleSet) {
Set<Permission> permissionSet = role.getPermissionSet();
if (CollectionUtils.isNotEmpty(permissionSet)){
for (Permission permission : permissionSet) {
permissionList.add(permission.getName());
}
}
}
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addStringPermissions(permissionList);
return null;
}
// 认证登录
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token; //将Token 降为 usernamePasswordToken
String username = usernamePasswordToken.getUsername(); //去除其中的username
User user = userService.findByUsername(username); //根据username 查询到 user 信息
return new SimpleAuthenticationInfo(user,user.getPassword(),this.getClass().getName());
}
}
3、基于Apache Shiro权限管理Case -- 密码规则校验器
说明:
具体:通过重写CredentialsMatcher 类,继承SimpleCredentialsMatcher类,重新定义 密码校验规则的重写
/**
* @ClassName CredentialMatcher 密码校验规则的重写
* @Description TODO
* @Author wushaopei
* @Date 2019/9/24 10:07
* @Version 1.0
*/
public class CredentialMatcher extends SimpleCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
UsernamePasswordToken token1 = (UsernamePasswordToken) token;
String passsword = new String(token1.getPassword());
String dbPassword = (String) info.getCredentials();
return this.equals(passsword,dbPassword);
}
}
4、基于Apache Shiro权限管理Case -- Shiro配置
创建 ShiroConfiguration 类 ,该类是 shiro 的配置类
@Configuration
public class ShiroConfiguration {
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager manager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
bean.setSecurityManager(manager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
bean.setLoginUrl("/login");
// 登录成功后要跳转的链接
bean.setSuccessUrl("/index");
// 未授权界面;
bean.setUnauthorizedUrl("/unauthorized");
// 拦截器.
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/index","authc");
filterChainDefinitionMap.put("/login","anon");
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}
//配置核心安全事务管理器
@Bean("securityManager")
public SecurityManager securityManager(@Qualifier("authRealm") AuthRealm authRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(authRealm);
return securityManager;
}
//配置自定义的权限登录器
@Bean("authRealm")
public AuthRealm authRealm(@Qualifier("credentialMatcher")CredentialMatcher matcher){
AuthRealm authRealm = new AuthRealm();
authRealm.setCredentialsMatcher(matcher); //给出自定密码比较器
return authRealm; //返回自定Realm
}
//配置自定义的密码比较器
@Bean("credentialMatcher") //生成 密码校验规则的Bean
public CredentialMatcher credentialMatcher(){
return new CredentialMatcher(); //返回密码校验规则实例
}
/**
* 开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持; Controller才能使用@RequiresPermissions
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager")SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
}
5、定义请求接口:包括首页、登录页、异常页面
@RequestMapping("/login")
public String login(){
return "login";
}
@RequestMapping("/index")
public String index(){
return "index";
}
@RequestMapping("/loginUser")
public String loginUser(@RequestParam("username")String username,
@RequestParam("password") String password,
HttpSession session){
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
User principal = (User) subject.getPrincipal();
session.setAttribute("user",principal);
return "index";
}catch (Exception e){
return "login";
}
}
6、定义jsp 页面,包括index.jsp、login.jsp、
spring:
mvc: ## jsp ##
view:
prefix: /pages/
suffix: .jsp
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1>欢迎登录</h1>
<form action="/loginUser" method="post">
<input type="text" name="username"><br>
<input type="password" name="password"><br>
<input type="submit" value="提交"><br>
</form>
</body>
</html>
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>index</title>
</head>
<body>
<h1>欢迎登录, ${user.username}</h1>
</body>
</html>
接口登录测试:
提交,认证通过
7、基于Apache Shiro权限管理Case -- Shiro配置 登录拦截
shiro 过滤器添加配置:
filterChainDefinitionMap.put("/loginUser","anon"); //登陸不用登陸認證
filterChainDefinitionMap.put("/**","user"); //其他接口要驗證是否登陸過用戶
测试:
除了登录功能不需要进行认证外,其他接口请求都需要进行用户是否登录的认证.
8、基于Apache Shiro权限管理Case -- Shiro配置 角色认证
在 ShiroConfiguration.java 进行配置:
filterChainDefinitionMap.put("/admin","roles[admin]"); //授权过程中认证角色为admin的用户才可以访问
在AuthRealm.java进行配置:
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 授权要先从 session 取出对象来
User user = (User) principals.fromRealm(this.getClass().getName()).iterator().next();
List<String> permissionList = new ArrayList<>();
List<String> roleNameList = new ArrayList<>(); //角色授权拦截。。。。。。。
Set<Role> roleSet = user.getRoles();
if (CollectionUtils.isNotEmpty(roleSet)){
for (Role role : roleSet) {
roleNameList.add(role.getName()); //角色授权拦截。。。。。。。。
Set<Permission> permissionSet = role.getPermissionSet();
if (CollectionUtils.isNotEmpty(permissionSet)){
for (Permission permission : permissionSet) {
permissionList.add(permission.getName());
}
}
}
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addStringPermissions(permissionList);
simpleAuthorizationInfo.addRoles(roleNameList); //角色授权拦截。。。。。。
return simpleAuthorizationInfo;
}
8、基于Apache Shiro权限管理Case -- Shiro配置 permission 认证
说明:具有指定 permission 功能的permission才可以访问,如有edit时,才可以访问,否则不能访问
在ShiroConfiguration.java类中 shiroFilter 方法中配置:
filterChainDefinitionMap.put("/edit","perms[edit]");//具有edit 的pemission 才可以访问