这是一篇头到尾的详细整合演示,让你直接复制就能运行的代码。
org.apache.shiro
shiro-spring-boot-web-starter
1.5.1
com.github.theborakompanioni
thymeleaf-extras-shiro
2.0.0
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.1
com.alibaba
druid
1.1.21
log4j
log4j
1.2.17
org.springframework.boot
spring-boot-starter-jdbc
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
runtime
junit
junit
4.12
org.projectlombok
lombok
true
springboot整合mybatis以及druid数据源请参考我的上一篇博客
application.yaml:(默认为application.properties,我将它改成了application.yaml格式,如果使用.yaml格式,请严格控制空格数量,不然必定报错)
server:
port: 8081
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/shirotest?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
password: 123456
username: root
###################以下为druid增加的配置###########################
type: com.alibaba.druid.pool.DruidDataSource
# 初始化连接池个数
initialSize: 5
# 最小连接池个数——》已经不再使用,配置了也没效果
minIdle: 2
# 最大连接池个数
maxActive: 20
# 配置获取连接等待超时的时间,单位毫秒,缺省启用公平锁,并发效率会有所下降
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 用来检测连接是否有效的sql,要求是一个查询语句。
# 如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用
validationQuery: SELECT 1 FROM DUAL
# 建议配置为true,不影响性能,并且保证安全性。
# 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
testWhileIdle: true
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testOnBorrow: false
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 通过别名的方式配置扩展插件,多个英文逗号分隔,常用的插件有:
# 监控统计用的filter:stat
# 日志用的filter:log4j
# 防御sql注入的filter:wall
filters: stat,wall,log4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
useGlobalDataSourceStat: true
#关闭thymeleaf模板缓存
thymeleaf:
cache: false
#配置mybatis
mybatis:
type-aliases-package: cn.kgc.zhx.pojo
mapper-locations: classpath:mapper/*.xml
配置log4j.properties(这里我使用的是shiro官方配置)
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# General Apache libraries
log4j.logger.org.apache=WARN
# Spring
log4j.logger.org.springframework=WARN
# Default Shiro logging
log4j.logger.org.apache.shiro=INFO
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
pojo包:
User类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String username;
private String password;
private String role;
private String perm;
}
dao包:
UserMapper接口:
@Mapper
@Repository("userMapper")
public interface UserMapper {
User selectUser(String username);
}
Usermapper.xml映射文件(映射文件我放在了resources下自己创建的mapper文件里):
<mapper namespace="cn.kgc.zhx.dao.UserMapper">
<select id="selectUser" parameterType="string" resultType="User">
select * from user where username=#{username}
select>
mapper>
service包:
UserService接口:
public interface UserService {
User selectUser(String username);
}
UserServiceImpl实现类:
@Service("userService")
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public User selectUser(String username) {
return userMapper.selectUser(username);
}
}
两个命名空间:xmlns:th=“http://www.thymeleaf.org”
xmlns:shiro=“http://www.thymeleaf.org/thymeleaf-extras-shiro”
(这里一定要看清楚哪个html在哪个包下)
templates.error包下:
500.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
出错了哦,您可以尝试<a href="/tologin">重新登录</a>
</body>
</html>
templates.user包下
add.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>add页面</h2>
</body>
</html>
public.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>公共页面</h2>
</body>
</html>
testano.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>这是一个测试shiro注解的页面</h2>
</body>
</html>
update.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>update页面</h2>
</body>
</html>
templates包下:
index.html:
<!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>
<div th:if="session.user==null">
<a th:href="@{/tologin}">登录</a>
</div>
<a th:href="@{/user/toadd}">add</a><br>
<a th:href="@{/user/toupdate}">update</a><br>
<a th:href="@{/user/topublic}">public</a><br>
<div shiro:hasPermission="perm:vip">
<label>vip会员才能看到这条信息</label>
</div>
<div shiro:hasRole="role:user">
<label>普通用户才能看到这条信息</label>
</div>
<a th:href="@{/testAno}">测试注解</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>
<form th:action="@{/login}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="提交">
<label th:text="${msg}"></label>
</form>
</body>
</html>
noauth.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
您没有权限访问该页面
</body>
</html>
controller层
TestController类(这里的核心关注点就是登录login()方法,其他的都是普通的跳转页面方法):
@Controller
public class TestController {
@RequestMapping("/tologin")
public String toLogin(){
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的内置登录方法,这是一个很复杂的shiro内部方法,不用深究为什么(因为我发现点进去也看不到源码,这应该就是shiro告诉我们会用就行)。
subject.login(token);
//登录成功
return "index";
}catch (UnknownAccountException e){
//未查询到用户名
model.addAttribute("msg","账号不存在");
return "login";
}catch (IncorrectCredentialsException e){
//用户名和密码不匹配
model.addAttribute("msg","密码不正确");
return "login";
}catch (LockedAccountException e){
//用户被锁定
model.addAttribute("msg","您的账号被锁定,请解除锁定后登录!");
return "login";
}
}
@RequestMapping("/user/toadd")
public String add(){
return "user/add";
}
@RequestMapping("/user/toupdate")
public String update() {
return "user/update";
}
@RequestMapping("/user/topublic")
public String publicpage() {
return "user/public";
}
@RequestMapping("/noauth")
public String noauth() {
return "noauth";
}
@RequestMapping("/testAno")
@RequiresRoles(value = "role:admin")
public String testAno() {
return "user/testano";
}
}
在编写核心配置类之前,我们先了解下shiro三大组件,了解了以后,代码就很容易编写和理解了。
shiro三大组件:
Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
UserRealm类:
这个类主要的职责就是,对用户进行认证和授权。
(执行顺序一般是先认证,再授权,不知道为啥重写方法的时候授权在上边,可能是我理解的不够透彻)
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
@Override
//授权(给用户分配权限)
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection){
// SimpleAuthorizationInfo是AuthorizationInfo的一个实现类,它将作为返回值
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
//拿到当前用户
Subject subject= SecurityUtils.getSubject();
//取出从认证方法传过来的user信息(注意 用户!=user,用户是一个抽象,user是一个具体)
User user=(User) subject.getPrincipal();
//将用户信息放到session里面,以便在前端页面使用
Session session=subject.getSession();
session.setAttribute("user",user);
//给用户授权
String role = user.getRole();
String perm = user.getPerm();
//给此用户添加一个角色
info.addRole(role);
//给此用户添加一个权限
info.addStringPermission(perm);
return info;
}
@Override
//认证(验证用户名,密码是否正确)
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取Controller层生成的令牌
UsernamePasswordToken userToken =(UsernamePasswordToken) authenticationToken;
//从令牌中取出用户名,查询此用户名是否存在
String username = userToken.getUsername();
User user = userService.selectUser(username);
if (user==null){
//如果不存在,返回null,controller层的subject.login(token)就会报UnknownAccountException 异常
return null;
}else {
//principal ,credentials,realmName
return new SimpleAuthenticationInfo(user,user.getPassword(),"userRealm");
}
}
}
ShiroConfig类:
shiro的核心配置类
@Configuration
public class ShiroConfig {
//创建userRealm对象
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
//创建DefaultWebSecurityManager(三大组件之一SecurityManager)
@Bean
public DefaultWebSecurityManager webSecurityManagerger(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
//关联userRealm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建ShiroFilterFactoryBean(这里就相当一shior的过滤器)
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("webSecurityManagerger") DefaultWebSecurityManager webSecurityManager){
ShiroFilterFactoryBean bean =new ShiroFilterFactoryBean();
//关联DefaultWebSecurityManager
bean.setSecurityManager(webSecurityManager);
//设置认证失败返回的登录页面
bean.setLoginUrl("/tologin");
//设置权限不够跳转的提示页面
bean.setUnauthorizedUrl("/noauth");
Map<String,String> filterMap=new HashMap<String, String>();
/**
*添加shiro内置过滤:
*anon 没有参数,表示可以匿名使用。
* authc:没有参数,表示需要认证(登录)才能使用
* roles:例子roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。
* perms:例子perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
* rest:例子rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。
* port:例子port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。
* authcBasic:没有参数,表示httpBasic认证
* ssl:没有参数,表示安全的url请求,协议为https
* user没有参数,表示必须存在用户,当登入操作时不做检查
*/
filterMap.put("/user/public","authc");
filterMap.put("/user/toupdate","perms[perm:vip]");
filterMap.put("/user/toadd","roles[role:admin]");
//权限配置的map集合
bean.setFilterChainDefinitionMap(filterMap);
return bean;
}
//整合thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
到此为止,整个项目就准备就绪了。
运行springboot容器
进入登录页面:http://localhost:8081/tologin
登录测试
admin用户访问测试(这里请参考ShiroConfig授权,TestController控制跳转,以及html页面代码)
主页面OK
add页面OK
update页面OK
public页面OK
通过注解访问控制OK
user用户访问测试
主页面OK
add和update页面无权访问
public页面OK
通过注解访问控制报500异常,我重写了500页面,进行引导,测试OK
到这里,一整套的整合就结束了!shiro比较于springscruity结构更加复杂,调用方式比较乱,但是并不难,需要多加练习。