Shiro安全框架的简单入门

Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。

Shiro官方10分钟入门

1. Shiro三个核心组件

image-20200205180115048

  1. Subject
    Subject:即“当前操作用发户”。意味着“当前跟软件交互的东西”。·可以把它认为是Shiro的“用户”概念。Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。

  2. SecurityManager
    SecurityManager: Shiro通过SecurityManager来管理内部组件实例,简单使用就是管理Subject了。

  3. Realm
    Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。Realm实质上是一个安全相关的DAO

  1. 导入依赖
  2. 配置文件
  3. 启动类

2. Shiro相关类介绍

(1)Authentication 认证 —- 用户登录
(2)Authorization 授权 — 用户具有哪些权限
(3)Cryptography 安全数据加密
(4)Session Management 会话管理
(5)Web Integration web系统集成
(6)Interations 集成其它应用,spring、缓存框架

3.整合springboot

简单的登陆验证实例

Demo项目结构

  1. pom.xml相关包的导入
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
  1. 相关配置类以及各层实现代码

编写ShiroConfig类,主要就是要配置三个Shiro的对象

在ShiroFilterFactoryBean 中设置拦截链

package com.x2yu.springbootshiro.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.HashMap;
import java.util.Map;

/**
* @Author: x2yu
* @Date: 2020/2/4 21:06
* @Describe
*/

@Configuration
public class ShiroConfig {

//ShiroFilterFactoryBean 3

//DefaultWebSecurityManager 2

//Realm 对象 需要自定义类 1

@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

//关联SecurityManager 设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

//添加shiro的内置过滤器 anon:无需认证 authc:必须认证 user:拥有记住我 perms:拥有对资源的 role:拥有某个角色

Map<String,String> filterMap = new HashMap<>();

filterMap.put("/user/add","perms[user:add]");//拥有授权可以进
filterMap.put("/user/update","authc");//认证可进

shiroFilterFactoryBean.setLoginUrl("/toLogin");//登陆界面

shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");//未授权页面

shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

return shiroFilterFactoryBean;
}

@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getUserRealm") UserRealm userRealm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//关联管理Realm
defaultWebSecurityManager.setRealm(userRealm);

return defaultWebSecurityManager;
}

@Bean
public UserRealm getUserRealm(){
return new UserRealm();
}

}

上面的UserRealm为自定义 在Realm中和数据库交互获取用户信息 权限

package com.x2yu.springbootshiro.config;

import com.x2yu.springbootshiro.pojo.User;
import com.x2yu.springbootshiro.service.UserService;
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.springframework.beans.factory.annotation.Autowired;

/**
* @Author: x2yu
* @Date: 2020/2/4 21:09
* @Describe
*/
public class UserRealm extends AuthorizingRealm {

@Autowired
UserService userService;

//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行授权方法");
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//简单测试 为所有都添加add权限 实际应该在数据库中查询
simpleAuthorizationInfo.addStringPermission("user:add");

return simpleAuthorizationInfo;
}

//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证方法");

UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

//链接查询数据库用户
User user = userService.queryUserByName(token.getUsername());
if(user == null){
return null;//抛出 UnknownAccountException
}
//密码认证
return new SimpleAuthenticationInfo("",user.getPwd(),"");
}
}

User对象类

package com.x2yu.springbootshiro.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* @Author: x2yu
* @Date: 2020/2/5 15:40
* @Describe
*/

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}

Service层和其实现层

public interface UserService {
public User queryUserByName(String name);
}


@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public User queryUserByName(String name) {
return userMapper.queryUserByName(name);
}
}

Mapper 根据姓名查询

@Mapper
@Repository
public interface UserMapper {
public User queryUserByName(String name);
}

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">
<mapper namespace="com.x2yu.springbootshiro.mapper.UserMapper">

<select id="queryUserByName" parameterType="String" resultType="User">
select * from mybatis.user where name = #{name}
</select>

</mapper>

数据库中数据

id	name    pwd
1 admin 123456
2 test 111111
3 root 12345

说到了数据库就顺便把配置文件标记贴一下

application.properties

#mybatis相关配置
mybatis.type-aliases-package=com.x2yu.springbootshiro.pojo
mybatis.mapper-locations=classpath:mapper/*.xml

application.yml 数据库连接相关配置

spring:
datasource:
username: root
password: 123456
#?serverTimezone=UTC解决时区的报错
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource

#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#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.Priority
#则导入 log4j 依赖即可,Maven 地址: https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
  1. 前端代码

    image-20200206144315053

    前端采用thymeleaf进行了简单的测试

add 和 update中都只是简单的提示 随便什么内容可以区分都行

index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
<hr>
<a th:href="@{/user/add}">add</a>|
<a th:href="@{/user/update}">update</a>
<a th:href="@{/logout}">logout</a>
</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>
<h1>登陆</h1>
<br>
<p th:text="${msg}"></p>
<form th:action="@{/login}">
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="text" name="password"></p>
<p>用户名:<input type="submit"></p>
</form>
</body>
</html>
4. 控制层 Controller
package com.x2yu.springbootshiro.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
* @Author: x2yu
* @Date: 2020/2/4 20:37
* @Describe
*/

@Controller
public class MyController {

@RequestMapping({"/","/index"})
public String toIndex(Model model){
model.addAttribute("msg","hello,Shiro");
return "index";
}

@RequestMapping({"/user/add"})
public String add(Model model){
return "user/add";
}

@RequestMapping({"/user/update"})
public String update(Model model){
return "user/update";
}

@RequestMapping({"/toLogin"})
public String toLogin(Model model){
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 e) {
model.addAttribute("msg","用户名不存在");
return "login";
} catch ( IncorrectCredentialsException ice) {
model.addAttribute("msg","密码错误");
return "login";
}
}

@RequestMapping("/noauth")
@ResponseBody
public String unauthorized(){
return "未授权";
}

@RequestMapping("/logout")
@ResponseBody
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "logout!";//返回登出提示
}
}

4. 简单测试

输入http://localhost:8080/

跳转首页

image-20200206145043418

此时是未登录状态 点击add 和 update 都会跳转到 login 页面

image-20200206145155342

id	name    pwd
1 admin 123456
2 test 111111
3 root 12345

从数据库中选一个登陆 如果输入密码不对提示密码错误

image-20200206145507607

输入错误账号 判断为没有此账号

image-20200206145419380

输入正确则可以正常访问 add 和 update

但是这里有一个问题,在 ShiroConfig中

filterMap.put("/user/add","perms[user:add]");//拥有授权可以进
filterMap.put("/user/update","authc");//认证可进

add 页面应该是有权限可进,update是登陆认证可进

授权是在UserRealm中 为了测试简单我为所有账号都授权了,实际应该查询数据库

SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addStringPermission("user:add");//授权

那我们注释掉这一段 重启

点击add 跳转了 http://localhost:8080/noauth 提示未授权 update页面正常进入

未授权页面设置在ShiroConfig中

shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");//未授权页面
@RequestMapping("/noauth")
@ResponseBody
public String unauthorized(){
return "未授权";
}

最后点击Logout 再返回点击add或者update的时候又会跳转到登陆界面了。

贴一个正常的用户Shiro验证应该的所需要的表

image-20200203161040105