搭建流程:
1、首先创建表,使用shiro制作权限验证至少要又用户表、角色表、角色权限表、菜单地址表。
表结构图式例:
user:
role:
menu:
permission:
2、pom文件依赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.syzw.shiro.test</groupId>
<artifactId>shiro_demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shiro_demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<!--去掉默认日志,加载别的日志 , 切换log4j2日志读取 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--log4j的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j</artifactId>
<version>1.3.8.RELEASE</version>
</dependency>
<!--
mybatis-plus-boot-starter里面包含了mybatis和spring进行整合的依赖,
所以不需要导入mabatis-spring的依赖了直接就可以使用了。
-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<!--使用注解的方式给实体类添加set,get等方法-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<!--shiro的核心依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<!--shiro和spring整合的依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!--jwt需要的依赖-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3、自定义Realm。
/**
* @program: shiro_demo
* @description:
* 自定义的Realm数据源,用于给安全管理器做鉴权认证使用,realm在主体对象Subject调用login进行认证的时候,
* Subject将认证委托给安全管理器SecurityManager,SecurityManager最终还是会到达这个地方,执行这里的认证逻辑的。
* @author: hyly
* @create: 2019-08- 19:46
*/
public class MyRealm extends AuthorizingRealm {
//操作用户
@Autowired
private UserMapper userMapper;
//操作角色表
@Autowired
private RoleMapper roleMapper;
//操作权限表
@Autowired
private PermissionMapper permissionMapper;
//具体的菜单权限
@Autowired
private MenuMapper menuMapper;
public MyRealm(){
//因为数据库中的密码做了散列,所以使用shiro的散列Matcher,如果数据库中没有使用md5加密的话,加上这个地方会报错
// this.setCredentialsMatcher(new HashedCredentialsMatcher(Md5Hash.ALGORITHM_NAME));
}
//
/**
* 执行授权业务逻辑,主要用于做权限认证,也就是那些可以访问,那些不能访问
* 这个地方就是用来给登录的角色进行赋权的,也就是说从这个地方获取登录的用户有那些权限,并且把这些权限告诉shiro,
* 然后shiro就可以和拦截器中的拦截规则去验证,你是否有访问权限了。
*
* 这个地方需要访问数据库,获取当前用的权限,添加到shiro中。
*
* 这个方法之后登录成功之后才会访问
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//创建简单的授权信息
SimpleAuthorizationInfo simpleAuthenticationInfo=new SimpleAuthorizationInfo();
String userName= (String) principals.getPrimaryPrincipal();
System.out.println(userName);
//授权
if(userName!=null){
QueryWrapper<User> userWrapper=new QueryWrapper<>();
userWrapper.eq("name",userName);
User user=userMapper.selectOne(userWrapper);
if(user!=null){
System.out.println(userName);
//查询当前登录用户的所有所有角色
QueryWrapper<Role> wrapper=new QueryWrapper<>();
wrapper.eq("id",user.getRole());
List<Role> roleList=roleMapper.selectList(wrapper);
//将遍历list,将list中role的的角色名取出,并且放到set集合中。
Set<String> roles=roleList.stream().map(Role::getName).collect(Collectors.toSet());
//将所有的角色信息添加进授权信息中
simpleAuthenticationInfo.setRoles(roles);
//查询出来角色对应的权限id
QueryWrapper<Permission> permissionWrapper=new QueryWrapper<>();
permissionWrapper.eq("id",user.getRole());
List<Permission> permissionList=permissionMapper.selectList(permissionWrapper);
//所有的menu的id,也就是具体菜单的id
Set<String> menuIds=permissionList.stream().map(Permission::getMenu).collect(Collectors.toSet());
if(menuIds.size()>0){
//根据权限id查询权限路径
QueryWrapper<Menu> menuWrapper=new QueryWrapper<>();
menuWrapper.in("id",menuIds);
List<Menu> menuUrls=menuMapper.selectList(menuWrapper);
//所有的菜单的url,也就是访问权限
Set<String> permission=menuUrls.stream().map(Menu::getUrl).collect(Collectors.toSet());
simpleAuthenticationInfo.setStringPermissions(permission);
//添加用户所对应的角色的所有权限
}
return simpleAuthenticationInfo;
}
}
return null;
}
/**
* 执行用户认证逻辑,也就是用户的登录,当subject主体对象调用loggin方法时,这个方法会被调用
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取用户传递过来的用户名和密码
String userName= (String) token.getPrincipal();
//由于shiro为了解决stirng不可变的特点,防止内存的可读性,因此将密码转成了char,所以也就有了shiro的加盐。
String password=new String((char[])token.getCredentials());
//查询数据库中账户
QueryWrapper<User> wrapper=new QueryWrapper<User>();
wrapper.eq("name",userName);
User user =userMapper.selectOne(wrapper);
if(user==null){
return null;
}
//创建这个SimpleAuthenticationInfo对象就是为了将用户名和密码转成token,将这些敏感信息交由shiro管理,
// return new SimpleAuthenticationInfo(user.getName(),user.getPassword(), ByteSource.Util.bytes(user.getSalt),getName());
//SimpleAuthenticationInfo这个是认证信息
//将数据库中的用户名和密码传递进去,shiro回去做匹配认证
// byte[] decode = Base64.getDecoder().decode(user.getSalt());
// ByteSource bytes = ByteSource.Util.bytes(decode);
return new SimpleAuthenticationInfo(user.getName(),user.getPassword(),ByteSource.Util.bytes(user.getSalt()),getName());
}
// //设置这个Realm支持的验证token策略
// @Override
// public boolean supports(AuthenticationToken token) {
// return super.supports(token);
//
例如,以下的只能支持JWTToken
return token instanceof JWTToken;
// }
}
4、配置shiro的配置文件。
/**
* @program: shiro_demo
* @description: 用于配置Shiro的安全管理器,Realm源以及一些shiro拦截器
* @author: hyly
* @create: 2019-08- 19:44
*/
@Configuration
public class ShiroConfig {
/**
* 设置shiro权限拦截器
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
//创建shiro拦截器工厂,就像spring-mvc的拦截器链一样,ShiroFilterFactoryBean这个对象就像过滤器链
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
//和jwt的token拦截验证绑定起来
Map<String, Filter> filter=new HashMap<>();
filter.put("jwt", new JwtFilter());
shiroFilterFactoryBean.setFilters(filter);
/**
* shiro内置过滤器:
* anon:无需认证(登录),可以直接访问。
* authc:必须进行认证才能访问。
* user:如果使用rememberMe的功能,可以直接访问呢。
* perms:该资源必须得到资源的权限才可以进行访问。
* role:该资源必须得到角色的权限才可以进行访问。
*
* * URL 匹配风格
* * 1). ?:匹配一个字符,如 /admin? 将匹配 /admin1,但不匹配 /admin 或 /admin/;
* * 2). *:匹配零个或多个字符串,如 /admin* 将匹配 /admin 或/admin123,但不匹配 /admin/1;
* * 2). **:匹配路径中的零个或多个路径,如 /admin/** 将匹配 /admin/a 或 /admin/a/b
* *
* * 配置身份验证成功,失败的跳转路径
*
*/
//获取拦截规则
Map<String,String> filterMap=NoVerify.getNoerifyList();
//当用户没有登录时跳转的界面
shiroFilterFactoryBean.setLoginUrl("/user/login2");
//设置用户没有权限的默认跳转界面
shiroFilterFactoryBean.setUnauthorizedUrl("/user/noAuthen");
//登录成功的路径
shiroFilterFactoryBean.setSuccessUrl("/index");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
/**
* 创建出来安全管理器,用于进行鉴权使用
* @param realm
* @return
*/
@Bean("securityManager")
public DefaultWebSecurityManager getDefaultSecurityManager(@Qualifier("myRealm")MyRealm realm){
//创建出来一个安全管理器
DefaultWebSecurityManager defaultWebSecurityManager=new DefaultWebSecurityManager();
//让创建出来的安全管理器使用自己自定义的realm源进行认证
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
/**
*创建自定义的realm源,供安全管理器使用进行鉴权,可以提供多个
* @return
*/
@Bean("myRealm")
public MyRealm createMyRealm(){
MyRealm myRealm=new MyRealm();
//认证匹配器,用于给前端明文密码加密,和数据库取出的密文密码做检验
myRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myRealm;
}
/**
* 配置Shiro生命周期处理器,让shiro自动加载很多默认的配置
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
/**
* 自动创建代理类,若不添加,Shiro的注解可能不会生效。
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new
DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
* 开启Shiro的注解
*
* 这个地方的@Qualifier("securityManager") DefaultWebSecurityManager securityManager可能会有问题,这个是我自己加的
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new
AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 凭证匹配器
*
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了 )
* 因为数据库的密码加密了,而前端的输入的密码是明文密码,这样子去和数据库取出的密码做比较显然是不配的,
* 因此就要设置加密规则,shiro在密码比较的时候,就会将前端的明文密码加密之后再进行对比。
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new
HashedCredentialsMatcher();
//设置加密规则
hashedCredentialsMatcher.setHashAlgorithmName(Md5Hash.ALGORITHM_NAME);
//设置系统凭证的编码转换格式,也就是密码转成什么格式去比对,默认为true,表示16进制,
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(false);
//设置散列次数
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
/**
* Shiro方言,支持Thymeleaf中使用shiro标签,如果在前端要使用这个shiro的标签,则需要导入Thymeleaf依赖
*/
// @Bean
// public ShiroDialect shiroDialect() {
// return new ShiroDialect();
// }
}
5、controller模拟跳转页面。
/**
* @program: shiro_demo
* @description: 用户模块
* @author: hyly
* @create: 2019-08- 21:14
*/
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/login")
@ResponseBody
public String login(String userName, String password){
//将前端的用户名和密码存放到shiro中,这里不需要加密,因为realm源中设置了CredentialsMatcher
//CredentialsMatcher认证匹配器中设置了加密规则
UsernamePasswordToken token=new UsernamePasswordToken();
token.setPassword(password.toCharArray());
token.setUsername(userName);
/**
这里的账号密码到时候会自动使用认证器中的盐给这个明文加盐,然后和数据库查询出来的密文做匹配。
*/
//获取用户主体对象
Subject subject= SecurityUtils.getSubject();
try{
//进行认证
subject.login(token);
}catch (Exception exception){
exception.printStackTrace();
return "登录失败,用户名或者密码不对";
}
//判断是否登录
if(subject.isAuthenticated()){
return "登录成功";
}else{
return "登录失败";
}
}
/**
* 用户登出
*/
@GetMapping("/logout")
public String logout(){
Subject subject= SecurityUtils.getSubject();
subject.logout();
return"用户已经登出";
}
/**
* 添加用户
* @param user
* @return
*/
@PostMapping("/add")
public Integer add(@RequestBody User user){
Random random=new Random();
String salt=random.nextInt()+"";
Md5Hash md5Hash2=new Md5Hash(user.getPassword());
System.out.println(md5Hash2.toBase64());
Md5Hash md5Hash=new Md5Hash(user.getPassword(),salt);
user.setSalt(salt);
user.setPassword(md5Hash.toBase64());
Integer reuslt = userMapper.insert(user);
return reuslt;
}
/**
* 获取用户列表
* @return
*/
@GetMapping("/getList")
public List<User> getList(){
QueryWrapper<User> wrapper=new QueryWrapper<>();
List<User> list=userMapper.selectList(wrapper);
return list;
}
/**
* 模拟错误错误页面
* @return
*/
@GetMapping("/error")
public String error(){
return "500错误页面";
}
/**
* 模拟用户没有权限页面
* @return
*/
@GetMapping("/noAuthen")
public String noAuthen(){
return "权限不足";
}
/**
* 模拟跳转用户登录页面
* @return
*/
@GetMapping("/login2")
public String login(){
return "用户登录页面";
}
@RequiresPermissions("/exam/paperManager")
@GetMapping("/paper")
public String paper(){
return "试卷管理";
}
}
6、启动项目,就可以测试了。