springboot集成shiro
springboot集成shiro实现权限控制
完整代码地址
项目结构
文章目录
1 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.shiro</groupId>
<artifactId>springboot-shiro</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!--mysql连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--springboot test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--springboot web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--code generator-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.0</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.4.0</version>
</dependency>
<!-- jwtjar-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2 jwt工具类-JwtUtil
public class JwtUtil {
/**
* 过期时间
*/
private static final long EXPIRE_TIME = 5 * 60 * 1000;
private static final String CLAIM_NAME = "username";
/**
*
* 生成token
*/
public static String createToken(String username, String password) {
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
//加密处理密码
Algorithm algorithm = Algorithm.HMAC256(password);
return JWT.create()
.withClaim(CLAIM_NAME, username)
.withExpiresAt(date)
.sign(algorithm);
}
/**
*
* 验证token
*/
public static boolean verify(String username, String dbPwd, String token) {
Algorithm algorithm = Algorithm.HMAC256(dbPwd);
JWTVerifier jwtVerifier = JWT.require(algorithm)
.withClaim(CLAIM_NAME, username).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
return false;
}
return true;
}
/**
*
* 从token中获取用户名
*/
public static String getUserName(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim(CLAIM_NAME).asString();
} catch (JWTDecodeException e) {
return null;
}
}
}
3 jwt类-JwtToken
public class JwtToken implements HostAuthenticationToken {
private String username;
private char[] password;
private String host;
private String token;
public JwtToken(String token) {
this.token = token;
}
public JwtToken(String username, char[] password) {
this(username, password, (String) null);
}
public JwtToken(String username, String password) {
this(username, (char[]) (null != password ? password.toCharArray() : null), (String) null);
}
public JwtToken(String username, char[] password, String host) {
this.username = username;
this.password = password;
this.host = host;
}
@Override
public String getHost() {
return host;
}
@Override
public Object getPrincipal() {
return this.getToken();
}
@Override
public Object getCredentials() {
return this.getToken();
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public char[] getPassword() {
return password;
}
public void setPassword(char[] password) {
this.password = password;
}
public void setHost(String host) {
this.host = host;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
4 自定义拦截器-JwtFilter
public class JwtFilter extends BasicHttpAuthenticationFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtFilter.class);
private static final String AUTHZ_HEADER = "token";
private static final String CHARSET = "UTF-8";
/**
* 处理未经验证的请求
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) {
boolean loggedIn = false;
if (this.isLoginAttempt(request, response)) {
loggedIn = this.executeLogin(request, response);
}
if (!loggedIn) {
this.sendChallenge(request, response);
}
return loggedIn;
}
/**
* 请求是否已经登录(携带token)
*/
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
String authzHeader = WebUtils.toHttp(request).getHeader(AUTHZ_HEADER);
return authzHeader != null;
}
/**
* 执行登录方法(由自定义realm判断,吃掉异常返回false)
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) {
String token = WebUtils.toHttp(request).getHeader(AUTHZ_HEADER);
if (null == token) {
String msg = "executeLogin method token must not be null";
throw new IllegalStateException(msg);
}
//交给realm判断是否有权限,没权限返回false交给onAccessDenied
JwtToken jwtToken = new JwtToken(token);
try {
this.getSubject(request, response).login(jwtToken);
return true;
} catch (AuthenticationException e) {
return false;
}
}
/**
* 构建未授权的请求返回,filter层的异常不受exceptionAdvice控制,这里返回401,把返回的json丢到response中
*/
@Override
protected boolean sendChallenge(ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = WebUtils.toHttp(response);
String contentType = "application/json;charset=" + CHARSET;
httpResponse.setStatus(401);
httpResponse.setContentType(contentType);
try {
String msg = "对不起,您无权限进行操作!";
RestResponse unauthentication = new RestResponse(msg, false, 401);
PrintWriter printWriter = httpResponse.getWriter();
printWriter.append(JSON.toJSONString(unauthentication));
} catch (IOException e) {
LOGGER.error("sendChallenge error,can not resolve httpServletResponse");
}
return false;
}
/**
* 请求前处理,处理跨域
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse
.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
httpServletResponse.setHeader("Access-Control-Allow-Headers",
httpServletRequest.getHeader("Access-Control-Request-Headers"));
// 跨域时,option请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
}
5 AuthRealm
public class AuthRealm extends AuthorizingRealm {
@Autowired
private IUserService iUserService;
/**
* 设置realm支持的authenticationToken类型
*/
@Override
public boolean supports(AuthenticationToken token) {
return null != token && token instanceof JwtToken;
}
/**
* 登陆认证
*
* @param authenticationToken jwtFilter传入的token
* @return 登陆信息
* @throws AuthenticationException 未登陆抛出异常
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
//getCredentials getPrincipal getToken 都是返回jwt生成的token串
String token = (String) authenticationToken.getCredentials();
//判断token是否可用
String username = JwtUtil.getUserName(token);
if (username == null) {
throw new AccountException("token invalid");
}
User loginUser = iUserService.getByUserName(username);
if (!JwtUtil.verify(username, loginUser.getPassword(), token)) {
throw new UnknownAccountException("Username or password error");
}
return new SimpleAuthenticationInfo(token, token, getName());
}
/**
* 授权认证
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String token = principalCollection.toString();
//根据token获取权限授权
String userName = JwtUtil.getUserName(token);
User loginUser = iUserService.getByUserName(userName);
//查询用户角色、权限
User userWithRoleMenuById = iUserService.getUserWithRoleMenuById(loginUser.getId());
//设置当前用户的角色
Set<String> roles=new HashSet() ;
for (Role role : userWithRoleMenuById.getRoles()) {
roles.add(role.getName());
}
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(roles);
//设置当前用户的权限
Set<String> menus=new HashSet<String>() ;
for (Menu menu : userWithRoleMenuById.getMenus()) {
menus.add(menu.getPermission());
}
authorizationInfo.setStringPermissions(menus);
return authorizationInfo;
}
}
6 ShiroConfig
@Configuration
public class ShiroConfig {
private static final String JWT_FILTER_NAME = "jwt";
/**
* 自定义realm,实现登录授权流程
*/
@Bean
public Realm authRealm() {
return new AuthRealm();
}
/**
* 配置securityManager 管理subject(默认),并把自定义realm交由manager
*/
@Bean
public DefaultSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(authRealm());
//非web关闭sessionManager(官网有介绍)
DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator storageEvaluator = new DefaultSessionStorageEvaluator();
storageEvaluator.setSessionStorageEnabled(false);
defaultSubjectDAO.setSessionStorageEvaluator(storageEvaluator);
securityManager.setSubjectDAO(defaultSubjectDAO);
return securityManager;
}
/**
* 拦截链
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setFilters(filterMap());
shiroFilterFactoryBean.setFilterChainDefinitionMap(definitionMap());
return shiroFilterFactoryBean;
}
/**
* 自定义拦截器,处理所有请求
*/
private Map<String, Filter> filterMap() {
Map<String, Filter> filterMap = new HashMap<>();
filterMap.put(JWT_FILTER_NAME, new JwtFilter());
return filterMap;
}
/**
* url拦截规则
*/
private Map<String, String> definitionMap() {
Map<String, String> definitionMap = new HashMap<>();
definitionMap.put("/login", "anon");
definitionMap.put("/**", JWT_FILTER_NAME);
return definitionMap;
}
/**
* 开启注解
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
// 强制使用cglib代理,防止和aop冲突
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean("authorizationAttributeSourceAdvisor")
public AuthorizationAttributeSourceAdvisor advisor(DefaultSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
7 全局异常处理
@RestControllerAdvice
public class ExceptionAdvice {
@ExceptionHandler({UnauthenticatedException.class, AuthenticationException.class})
public RestResponse unauthenticatedException() {
RestResponse builder = new RestResponse("对不起,您还未登录!",false,403);
return builder;
}
@ExceptionHandler(UnauthorizedException.class)
@ResponseBody
public RestResponse unauthorizedException() {
RestResponse builder = new RestResponse("对不起,您没权限操作!",false,401);
return builder;
}
@ExceptionHandler(UnknownAccountException.class)
@ResponseBody
public RestResponse unknownAccountException() {
RestResponse builder = new RestResponse("登陆失败,用户名或密码错误!",false,403);
return builder;
}
}
8 OAuthConstant
public interface OAuthConstant {
interface Roles {
String USER = "user";
String ADMIN = "admin";
}
interface Permissions {
String SELECT = "select";
String INSERT = "insert";
}
interface Code {
int OK = 200;
int UNAUTHENTICATED = 401;
int UNAUTHORIZED = 403;
int NOT_FOUND = 404;
int SERVER_ERROR = 500;
int BUSINESS_ERROR = 600;
}
}
9 RestResponse
@Data
public class RestResponse {
private String msg;
private boolean success;
private Object data;
private Integer code;
public RestResponse(String msg, boolean success, Object data, Integer code) {
this.msg = msg;
this.success = success;
this.data = data;
this.code = code;
}
public RestResponse(String msg, boolean success, Integer code) {
this.msg = msg;
this.success = success;
this.code = code;
}
}
10 登录controller
@RestController
public class SysUserController {
@Autowired
private ISysUserService loginUserService;
@PostMapping("/login")
public RestResponse login(String username,String password) {
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
RestResponse builder = new RestResponse("账号和密码不能为空!",false,403);
return builder;
}
SysUser user = loginUserService.findByUserName(username);
if (null == user || !password.equals(user.getPassword())) {
throw new UnknownAccountException("用户名和密码错误");
}
String msg = "登录成功,请妥善保管您的token,有效期5分钟!";
return new RestResponse(msg,true, JwtUtils.createToken(username, password),200);
}
}
11 资源controller
@RestController
public class RestApiController {
@GetMapping("/find")
@RequiresPermissions(OAuthConstant.Permissions.SELECT)
public RestResponse find() {
return new RestResponse("find success",true,"find success",200);
}
@GetMapping("/list")
@RequiresRoles(OAuthConstant.Roles.USER)
public RestResponse list() {
return new RestResponse("list success",true,"list success",200);
}
}
12 sql
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for menu
-- ----------------------------
DROP TABLE IF EXISTS `menu`;
CREATE TABLE `menu` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单名称',
`permission` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限名称',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of menu
-- ----------------------------
INSERT INTO `menu` VALUES (1, '查询', 'select');
-- ----------------------------
-- Table structure for menu_role
-- ----------------------------
DROP TABLE IF EXISTS `menu_role`;
CREATE TABLE `menu_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`menu_id` int(11) NULL DEFAULT NULL,
`role_id` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `menu_role_ibfk_1`(`menu_id`) USING BTREE,
INDEX `menu_role_ibfk_2`(`role_id`) USING BTREE,
CONSTRAINT `menu_role_ibfk_1` FOREIGN KEY (`menu_id`) REFERENCES `menu` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `menu_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of menu_role
-- ----------------------------
INSERT INTO `menu_role` VALUES (1, 1, 1);
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'user');
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'hrID',
`name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, '张三', 'admin', 'admin');
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NULL DEFAULT NULL,
`role_id` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `user_role_ibfk_1`(`user_id`) USING BTREE,
INDEX `user_role_ibfk_2`(`role_id`) USING BTREE,
CONSTRAINT `user_role_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
CONSTRAINT `user_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1, 1);
SET FOREIGN_KEY_CHECKS = 1;
13 测试