感谢原链接教你 Shiro 整合 SpringBoot,避开各种坑 - 简书
本文做了理解处理,注:本文只简要说明实现方式,不涉及原理介绍
1、引入依赖
<!--shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
2、重写用户认证类CustomRealm,继承shiro的AuthorizingRealm类,目的是利用数据库中的user数据重写身份认证方法和权限认证方法,注:这里的UserMapper为普通的mapper类
import com.example.xxljobdemo.mapper.UserMapper;
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.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.Set;
public class CustomRealm extends AuthorizingRealm {
private UserMapper userMapper;
@Autowired
private void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
/**
* 获取身份验证信息
* Shiro中,最终是通过 Realm 来获取应用程序中的用户、角色及权限信息的。
*
* @param authenticationToken 用户身份信息 token
* @return 返回封装了用户信息的 AuthenticationInfo 实例
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("————身份认证方法————");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 从数据库获取对应用户名密码的用户
String password = userMapper.getPassword(token.getUsername());
if (null == password) {
throw new AccountException("用户名不正确");
} else if (!password.equals(new String((char[]) token.getCredentials()))) {
throw new AccountException("密码不正确");
}
return new SimpleAuthenticationInfo(token.getPrincipal(), password, getName());
}
/**
* 获取授权信息
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("————权限认证————");
String username = (String) SecurityUtils.getSubject().getPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//获得该用户角色
String role = userMapper.getRole(username);
Set<String> set = new HashSet<>();
//需要将 role 封装到 Set 作为 info.setRoles() 的参数
set.add(role);
//设置该用户拥有的角色
info.setRoles(set);
return info;
}
}
@Repository
public interface UserMapper {
String getPassword(String username);
String getRole(String username);
}
<?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.example.xxljobdemo.mapper.UserMapper">
<select id="getUsers" resultType="com.example.xxljobdemo.vo.User">
select id,username from user
</select>
<select id="getPassword" resultType="string">
select password from user where username = #{username}
</select>
<select id="getRole" resultType="string">
select role from user where username = #{username}
</select>
</mapper>
数据库字段
3、配置拦截规则的shiro配置类ShiroConfig
两个关键点:SecurityManager的包一定要引入import org.apache.shiro.mgt.SecurityManager;
这里的/notLogin和/notRole是自定义的controller接口
其中anon、roles[user]、authc等是shori定义的匹配规则
Filter | 解释 |
---|---|
anon | 无参,开放权限,可以理解为匿名用户或游客 |
authc | 无参,需要认证 |
logout | 无参,注销,执行后会直接跳转到shiroFilterFactoryBean.setLoginUrl(); 设置的 url |
authcBasic | 无参,表示 httpBasic 认证 |
user | 无参,表示必须存在用户,当登入操作时不做检查 |
ssl | 无参,表示安全的URL请求,协议为 https |
perms[user] | 参数可写多个,表示需要某个或某些权限才能通过,多个参数时写 perms["user, admin"],当有多个参数时必须每个参数都通过才算通过 |
roles[admin] | 参数可写多个,表示是某个或某些角色才能通过,多个参数时写 roles["admin,user"],当有多个参数时必须每个参数都通过才算通过 |
rest[user] | 根据请求的方法,相当于 perms[user:method],其中 method 为 post,get,delete 等 |
port[8081] | 当请求的URL端口不是8081时,跳转到schemal://serverName:8081?queryString 其中 schmal 是协议 http 或 https 等等,serverName 是你访问的 Host,8081 是 Port 端口,queryString 是你访问的 URL 里的 ? 后面的参数 |
常用的主要就是 anon,authc,user,roles,perms 等
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.shiro.mgt.SecurityManager;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射
//这里的/notLogin和/notRole是自定义的controller接口
shiroFilterFactoryBean.setLoginUrl("/notLogin");
// 设置无权限时跳转的 url;
shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
// 设置拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//用户,需要角色权限 “user”
filterChainDefinitionMap.put("/user/**", "roles[user]");
//管理员,需要角色权限 “admin”
filterChainDefinitionMap.put("/admin/**", "roles[admin]");
//开放登陆接口
filterChainDefinitionMap.put("/login", "anon");
//其余接口一律拦截
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
System.out.println("Shiro拦截器工厂类注入成功");
return shiroFilterFactoryBean;
}
/**
* 注入 securityManager
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(customRealm());
return securityManager;
}
/**
* 自定义身份认证 realm;
* <p>
* 必须写这个类,并加上 @Bean 注解,目的是注入 CustomRealm,
* 否则会影响 CustomRealm类 中其他类的依赖注入
*/
@Bean
public CustomRealm customRealm() {
return new CustomRealm();
}
}
4、登录注销和鉴权不通过的接口
import com.example.xxljobdemo.mapper.UserMapper;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LoginController {
@Autowired
private UserMapper userMapper;
@RequestMapping(value = "/notLogin", method = RequestMethod.GET)
public String notLogin() {
return "您尚未登陆!";
}
@RequestMapping(value = "/notRole", method = RequestMethod.GET)
public String notRole() {
return "您没有权限!";
}
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout() {
Subject subject = SecurityUtils.getSubject();
//注销
subject.logout();
return "成功注销!";
}
/**
* 登陆
*
* @param username 用户名
* @param password 密码
*/
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(String username, String password) {
// 从SecurityUtils里边创建一个 subject
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备 token(令牌)
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 执行认证登陆
subject.login(token);
//根据权限,指定返回数据
String role = userMapper.getRole(username);
if ("user".equals(role)) {
return "欢迎登陆";
}
if ("admin".equals(role)) {
return "欢迎来到管理员页面";
}
return "权限错误!";
}
}
5、user和admin的权限测试接口
//管理员
@RestController
@RequestMapping("/admin")
public class AdminController {
@RequestMapping(value = "/getMessage", method = RequestMethod.GET)
public String getMessage() {
return "您拥有管理员权限,可以获得该接口的信息!";
}
}
//普通登陆用户
@RestController
@RequestMapping("/user")
public class UserController{
@RequestMapping(value = "/getMessage", method = RequestMethod.GET)
public String getMessage() {
return "您拥有用户权限,可以获得该接口的信息!";
}
}
6、启动项目测试,场景:非登录状态下访问/admin/getMessage接口,登录admin账户后访问/admin/getMessage接口,登录admin账户访问/user/getMassage的接口,注销后再次访问admin/getMessage接口.如果满足预期则配置成功