Shiro
什么是Shiro?
- Apache Shiro是一个Java的安全(权限)框架。
- Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。
- Shiro可以完成, 认证,授权,加密,会话管理,Web集成,缓存等。
1、pom.xml
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
2、log4j.properties
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
3、引入shiro.inn和Quickstart.java 文件
常用方法:
//获取当前的用户对象 Subject
Subject currentUser = SecurityUtils.getSubject();
//通过当前用户拿到session
Session session = currentUser.getSession();
//测试当前的用户是否被认证
currentUser.isAuthenticated();
currentUser.getPrincipal()//their identifying principal
currentUser.login(token); //执行登录操作
//test a role:
currentUser.hasRole("schwartz");
//test a typed permission (not instance-level)
//粗粒度
currentUser.isPermitted("lightsaber:wield")
//a (very powerful) Instance Level permission:
//细粒度
currentUser.isPermitted("winnebago:drive:eagle5")
//注销
currentUser.logout();
Springboot集成shiro
环境搭建
1、pom.xml 导入shiro和spring的jar包
<!--Thymeleaf-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
<!--shiro整合spring的包-->
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
命名空间:
xmlns:th="http://www.thymeleaf.org"
Subject —用户
Security- —管理所有用户
Realm—连接数据
2、配置文件(UserRealm)编写
1、 UserRealm
package com.kuang.config;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
//自定义的UserRealm 需要继承 AuthorizingRealm
public class UserRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=》授权");
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了=》认证");
return null;
}
}
2、ShiroConfig
package com.kuang.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;
/*倒着配*/
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean 3
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
return bean;
}
//DafaultWebSecurityManager ,从spring中取得UserRealm,将二者关联起来 2
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
//关联UserRealm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建 realm 对象,需要自定义类
//将一个类bean注入到Configuration里面, 被spring接管 1
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
实现登录拦截
拦截请求在ShiroConfig配置类中实现
//添加shiro的内置过滤器
/*
* anno: 无需验证就可以访问
* authc: 必须认证了才能访问
* user: 必须开启记住我 功能才能访问
* perms:拥有对某个资源的权限才能访问
* role: 拥有某个角色权限才能访问
* */
//ShiroFilterFactoryBean 3
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器
/*
* anno: 无需验证就可以访问
* authc: 必须认证了才能访问
* user: 必须开启记住我 功能才能访问
* perms:拥有对某个资源的权限才能访问
* role: 拥有某个角色权限才能访问
* */
Map<String,String> filterMap=new LinkedHashMap<>();
filterMap.put("/user/add","authc");
filterMap.put("/user/update","authc");
bean.setFilterChainDefinitionMap(filterMap);
//设置登录的请求
bean.setLoginUrl("/toLogin");
return bean;
}
Shiro实现用户认证
认证在UserRealm类中实现
1、login.html
1、MyController
//登录表单提交到这里
@RequestMapping("/login")
public String login(String username,String password,Model model){
//获得当前的用户
Subject currentUser = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try{
currentUser.login(token); //执行登陆方法,如果没有异常就说明ok了
return "index";
} catch (UnknownAccountException e) { //用户名不存在
model.addAttribute("msg","用户名错误");
return "login";
}catch (IncorrectCredentialsException e){
model.addAttribute("msg","密码错误"); //密码不存在
return "login";
}
}
2、UserRealm
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了=》认证");
//用户名,密码 数据库中取
String name="root";
String password="111";
UsernamePasswordToken userToken=(UsernamePasswordToken)token;
if (!userToken.getUsername().equals(name)){
return null; //抛出异常 UnknownAccountException
}
//密码认证,shiro做了
return new SimpleAuthenticationInfo("",password,"");
}
shiro整合mybatis
1、pom.xml
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--引入mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2、配置文件
application.yaml
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/springboot?serverTimezone=GMT&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#url: jdbc:mysql://localhost:3306/springboot?serverTimezone=GMT
#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
#连接失败后重试次数
connection-error-retry-attempts: 5
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
application.properties
mybatis.type-aliases-package=com.kuang.pojo
mybatis.mapper-locations=classpath:mapper/*.xml
3、user实体类、UserMapper接口书写,Usermapper.xml书写
package com.kuang.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String username;
private String password;
}
package com.kuang.mapper;
import com.kuang.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Mapper //这个注解表示这是一个mybatis的mapper类
@Repository //标明是dao
public interface UserMapper {
User queryUserByName(String name);
}
<?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.kuang.mapper.UserMapper">
<select id="queryUserByName" resultType="User">
select * from user where username=#{name};
</select>
</mapper>
改造之前的UserRealm配置文件连接数据库
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了=》认证");
UsernamePasswordToken userToken=(UsernamePasswordToken)token;
//用户名,密码 数据库中取
User user = userService.queryUserByName(userToken.getUsername());
if (user==null){
return null; //抛出异常 UnknownAccountException
}
//密码认证,shiro做了
return new SimpleAuthenticationInfo("",user.getPassword(),"");
}
Shiro请求授权实现
@RequestMapping("/noauch")
@ResponseBody
public String auth(){
return "没有权限访问";
}
授权
获取当前用户权限
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=》授权");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//拿到当前登录用户
Subject subject = SecurityUtils.getSubject();
User currentUser= (User) subject.getPrincipal();
//设置当前用户的权限
info.addStringPermission(currentUser.getPerms());
return info;
}
设置链接权限
//授权,指定该请求需要有该权限才能访问, 没有权限的话会跳转到未授权页面
filterMap.put("/user/add","perms[user:add]"); //add需要有user:add权限
filterMap.put("/user/update","perms[user:update]"); //update需要有user:update
修改表结构
实体表也要改
注意:
想实现用户登录后只显示他拥有权限的链接,需要shiro整合thymeleaf
1、导入jar包
<!--shiro整合thymeleof-->
<!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
2、配置
//整合thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
shiro和thymeleaf 命名空间:xmlns:shiro=“http://www.pollix.at/thymeleaf/shiro”
<div shiro:notAuthenticated></div> //表示没有身份验证时显示信息
完整的ShiroConfig
package com.kuang.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
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;
/*倒着配*/
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean 3
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器
/*
* anno: 无需验证就可以访问
* authc: 必须认证了才能访问
* user: 必须开启记住我 功能才能访问
* perms:拥有对某个资源的权限才能访问
* role: 拥有某个角色权限才能访问
* */
//拦截
Map<String,String> filterMap=new LinkedHashMap<>();
//授权,指定该请求需要有该权限才能访问, 没有权限的话会跳转到未授权页面
filterMap.put("/user/add","perms[user:add]"); //add需要有user:add权限
filterMap.put("/user/update","perms[user:update]"); //update需要有user:update
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
//设置登录的请求
bean.setLoginUrl("/toLogin");
//设置为没有权限的页面
bean.setUnauthorizedUrl("/noauch");
return bean;
}
//DafaultWebSecurityManager ,从spring中取得UserRealm,将二者关联起来 2
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
//关联UserRealm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建 realm 对象,需要自定义类
//将一个类bean注入到Configuration里面, 被spring接管 1
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
//整合thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
完整的UserRealm
package com.kuang.config;
import com.kuang.pojo.User;
import com.kuang.service.UserService;
import com.kuang.service.UserServiceImpl;
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;
//自定义的UserRealm 需要继承 AuthorizingRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=》授权");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//拿到当前登录用户
Subject subject = SecurityUtils.getSubject();
User currentUser= (User) subject.getPrincipal();
//设置当前用户的权限
info.addStringPermission(currentUser.getPerms());
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了=》认证");
UsernamePasswordToken userToken=(UsernamePasswordToken)token;
//用户名,密码 数据库中取
User user = userService.queryUserByName(userToken.getUsername());
if (user==null){
return null; //抛出异常 UnknownAccountException
}
//密码认证,shiro做了
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
}