以下是我个人从零开始在springboot项目中去整合Shiro框架。
我以项目结构的方式去介绍了:
先贴个项目结构目录图:
首先是pom文件:
这个pom文件,该说明的我都在注释上做了说明,请认真阅读。
配置完所需要用的jar包后,我们整合Shiro框架需要做的第一步是什么呢?
就是建一个shiro文件夹,在这个文件夹里面,我们需要建2个类:
一个是config配置类(顾名思义,就是各自配置),一个是自定义的Realm域类(这么说吧,就是个做类似连接效果的东西,shiro里面很多东西都得经过这个圈儿)
ShiroConfig类:
package com.shirodemo.shiro;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.cache.ehcache.EhCacheManager;
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;
/**
* @Author: JCccc
* @CreateTime: 2018-10-25
* @Description:
*/
@Configuration
public class ShiroConfig {
/**
* Subject: 用户主体(把操作交给SecurityManager)
* SecurityManager: 安全管理器(关联Realm)
* Realm: Shiro连接数据的桥梁
*/
//一. 创建ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加Shiro内置过滤器
/**
* Shiro内置过滤器,可以实现权限相关的拦截器
* 常用的过滤器:
* anon:无需认证(登录)可以访问
* authc:必须认证才可以访问
* user:如果使用rememberMe的功能可以直接访问
* perms:该资源必须得到资源权限才可以访问
* role:该资源必须得到角色权限才可以访问
*
*
*
*/
Map
/*filterMap.put("/add","authc");
filterMap.put("/update","authc");*/
//使用通配方式统一配置拦截的URL
// 注意:*******************下面的是按顺序执行的**********************
filterMap.put("/testThymeleaf","anon");
//放行login.html
filterMap.put("/login","anon");
//授权过滤器 ,以下为例 加上了授权perms 后,那么自动会跳转到 执行认证授权AuthenticationInfo doGetAuthenticationInfo
// 当授权拦截时,会自动跳转到未授权提示页面
filterMap.put("/add","perms[user-add]");
filterMap.put("/update","perms[user-update]");
filterMap.put("/*","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//修改拦截后返回的登录页面
shiroFilterFactoryBean.setLoginUrl("/toLogin");
//设置未授权提示页面
shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");
return shiroFilterFactoryBean;
}
//二. 创建DefaultWebSecurityMangager
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
//关联Realm
securityManager.setRealm(userRealm);
//注入缓存管理器;
securityManager.setCacheManager(ehCacheManager());
//这个如果执行多次,也是同样的一个对象;
return securityManager;
}
//三. 创建Realm
@Bean(name = "userRealm")
public UserRealm getRaalm(){
return new UserRealm();
}
/**
* 配置ShiroDialect,用于thymeleaf和shiro标签配合使用
*/
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
/**
* shiro缓存管理器;
* 需要注入对应的其它的实体类中:
* 1、安全管理器:securityManager
* 可见securityManager是整个shiro的核心;
* @return
*/
@Bean
public EhCacheManager ehCacheManager(){
System.out.println("ShiroConfiguration.getEhCacheManager()");
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:config/shiro-ehcache.xml");
return cacheManager;
}
}
这个配置类里面配置的东西以及意义,我也做了相关的详细注释。至于你想更深入了解,那请更深入地自己去网上了解了。
然后是自定义的Realm,我这边定义的名字叫UserRealm:
package com.shirodemo.shiro;
import com.shirodemo.pojo.User;
import com.shirodemo.service.UserService;
import com.shirodemo.util.StringUtils;
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.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @Author: JCccc
* @CreateTime: 2018-10-25
* @Description:这是一个自定义的Realm类,继承AuthorizingRealm类
*/
public class UserRealm extends AuthorizingRealm{
@Autowired
UserService userService;
/**
* 执行授权逻辑
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("执行授权逻辑!");
//给资源进行授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//先到数据库去查询当前登录用户的授权字符串
Subject subject= SecurityUtils.getSubject();
User user=(User)subject.getPrincipal();
User userNow=userService.queryUserInfoById(user.getId());
System.out.println("权限是L:"+userNow.getPerms());
//添加资源的授权字符串
info.addStringPermission(userNow.getPerms());
clearCachedAuthorizationInfo();
return info;
}
/**
* 执行认证逻辑
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行认证逻辑!");
//编写Shiro的判断逻辑,判断用户名和密码
//1.判断用户名
UsernamePasswordToken tokenNow=(UsernamePasswordToken)token;
User user=userService.queryUserInfoByaccountname(tokenNow.getUsername());
if(StringUtils.isNull(user)){
//用户名不存在
//此刻shiro会抛出一个UnknownAccountException,表示用户名不存在
return null;
}
// String password=user.getPassword();
//2.判断密码
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
/**
* 清理缓存权限
*/
public void clearCachedAuthorizationInfo()
{
this.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
}
}
配置完这两个大核心,接下来,再加上一个配置缓存的xml,那么关于shiro的配置部分(除了filter,这里我没用到)就大功告成了。
缓存配置的xml:
updateCheck="false">
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"/>
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true"/>
overflowToDisk="false"
eternal="false"
diskPersistent="false"
timeToLiveSeconds="300"
timeToIdleSeconds="600"
statistics="true"/>
差点忘了yml文件了:
server:
servlet:
context-path: /
port: 8012
spring:
datasource:
druid:
# 数据库访问配置, 使用druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/shirotest?useSSL=false&useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
# 连接池配置
initial-size: 5
min-idle: 5
max-active: 20
# 连接等待超时时间
max-wait: 30000
# 配置检测可以关闭的空闲连接间隔时间
time-between-eviction-runs-millis: 60000
# 配置连接在池中的最小生存时间
min-evictable-idle-time-millis: 300000
validation-query: select '1' from dual
test-while-idle: true
test-on-borrow: false
test-on-return: false
# 打开PSCache,并且指定每个连接上PSCache的大小
pool-prepared-statements: true
max-open-prepared-statements: 20
max-pool-prepared-statement-per-connection-size: 20
# 配置监控统计拦截的filters, 去掉后监控界面sql无法统计, 'wall'用于防火墙
filters:
commons-log.connection-logger-name: stat,wall,log4j
useGlobalDataSourceStat: true
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# StatViewServlet配置
stat-view-servlet:
enabled: true
# 访问路径为/druid时,跳转到StatViewServlet
url-pattern: /druid/*
# 是否能够重置数据
reset-enable: false
# 需要账号密码才能访问控制台
login-username: root
login-password: 123456
# IP白名单
# allow: 127.0.0.1
# IP黑名单(共同存在时,deny优先于allow)
# deny: 192.168.1.218
# 配置StatFilter
filter:
stat:
log-slow-sql: true
mybatis:
# type-aliases扫描路径
type-aliases-package: com.shirodemoe.pojo
# mapper xml实现扫描路径
mapper-locations: classpath:mybatis/mapper/*.xml
property:
order: BEFORE
配置完关于shiro的一些条条框框,那么我们可以直接开始使用了。
当然我这里的使用是指,已经配置了mybatis啊、Durid连接池啊、Thymeleaf啊那些其他的东西。
开始使用,先从数据库开始:
数据库表,一张(数据库连接的配置信息在yml文件里可以看,当然这个应该自己就会):
然后就是回到我们熟悉的SSM框架了其实。
废话不多说,
先上个pojo的类:
package com.shirodemo.pojo;
/**
* @Author: JCccc
* @CreateTime: 2018-10-26
* @Description:
*/
public class User {
private Integer id;
private String accountname;
private String password;
private String perms;
@Override
public String toString() {
return "User{" +
"id=" + id +
", accountname='" + accountname + '\'' +
", password='" + password + '\'' +
", perms='" + perms + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getAccountname() {
return accountname;
}
public void setAccountname(String accountname) {
this.accountname = accountname;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPerms() {
return perms;
}
public void setPerms(String perms) {
this.perms = perms;
}
}
然后到了dao层了,也就是mapper:
package com.shirodemo.mapper;
import com.shirodemo.pojo.User;
import org.apache.ibatis.annotations.Mapper;
/**
* @Author: JCccc
* @CreateTime: 2018-10-26
* @Description:
*/
@Mapper
public interface UserMapper {
User queryUserInfoByaccountname(String accountname);
User queryUserInfoById(Integer id);
}
接着,就是UserMapper.xml:
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
然后是UserService:
package com.shirodemo.service;
import com.shirodemo.pojo.User;
/**
* @Author: JCccc
* @CreateTime: 2018-10-26
* @Description:
*/
public interface UserService {
User queryUserInfoByaccountname(String accountname);
User queryUserInfoById(Integer id);
}
再是impl实现类:
package com.shirodemo.service.impl;
import com.shirodemo.mapper.UserMapper;
import com.shirodemo.pojo.User;
import com.shirodemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @Author: JCccc
* @CreateTime: 2018-10-26
* @Description:
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
public UserMapper userMapper;
@Override
public User queryUserInfoByaccountname(String accountname){
return userMapper.queryUserInfoByaccountname(accountname);
}
@Override
public User queryUserInfoById(Integer id) {
return userMapper.queryUserInfoById(id);
}
}
最后是相关接口,UserController.java:
package com.shirodemo.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.subject.Subject;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/**
* @Author: JCccc
* @CreateTime: 2018-10-25
* @Description:
*/
@Controller
public class UserController {
@ResponseBody
@RequestMapping("/hello")
public String helloContro(){
return "hello man";
}
@RequestMapping("/add")
public String add(){
return "/user/add";
}
@RequestMapping("/update")
public String update(){
return "/user/update";
}
//用于拦截完之后去登录页面
@RequestMapping("/toLogin")
public String login(){
return "login";
}
//用于未授权识别后跳转的提示页面
@RequestMapping("/noAuth")
public String noAuth(){
return "noAuth";
}
//测试页面主页
@RequestMapping("/testThymeleaf")
public String testThymeleafController(Model model){
model.addAttribute("name","测试模板引擎");
return "test";
}
@RequestMapping("/login")
public String loginController(String accountname, String password, Model model, HttpServletRequest request){
/**
*
* 使用Shiro编写认证操作
*/
System.out.println("开始执行认证"+"name为"+accountname);
//1.获取Subject
Subject subject= SecurityUtils.getSubject();
//2.封装用户账号信息数据
UsernamePasswordToken token=new UsernamePasswordToken(accountname,password);
//3.执行登录方法
try {
subject.login(token);
//当执行subject的login方法,那么就必定会到shiro里面的认证操作方法里面AuthenticationInfo doGetAuthenticationInfo
//登录成功
//跳转到test.html
return "test";
}
catch (UnknownAccountException e) {
//e.printStackTrace();
//如果是catch到UnknownAccountException异常,那么就是用户名不存在
model.addAttribute("msg","用户名不存在");
//如果这里使用重定向,那么msg将不会跟着过去下个函数方法
return "login";
}
catch (IncorrectCredentialsException e) {
// e.printStackTrace();
//登陆失败,密码错误
model.addAttribute("msg","密码错误");
//如果这里使用重定向,那么msg将不会跟着过去下个函数方法
return "login";
}
}
}
下面是一些需要用到的简单的html页面:
test.html :