springboot整合shiro和swagger2实现前后分离
中国加油,武汉加油!
篇幅较长,配合右边目录观看
项目准备
- 创建springboot项目nz1904-springboot-08-shiro-swagger
- 加入Web的SpringWeb依赖和Lombox依赖
- 导相关依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.54</version> </dependency> <!--swagger2相关依赖--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version> </dependency> <!-- spring跟shiro整合依赖--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency>
1. 案例
1.1 定义ShiroConfig和SwaggerConfig
package com.wpj.config;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@SpringBootConfiguration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createApi(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.wpj.shiro.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("这里是springboot整合shiro和swagger2前后分离的功能文档")
.description("这里是nz1904-springboot-08-shiro-swagger测试用的")
.contact("杰KaMi")
.version("v1.0")
.build();
}
}
package com.wpj.config;
import org.springframework.boot.SpringBootConfiguration;
@SpringBootConfiguration
public class ShiroConfig {
}
1.2 扩展原生Token
package com.wpj.token;
import org.apache.shiro.authc.UsernamePasswordToken;
public class CustomToken extends UsernamePasswordToken {
private String token; // 用户身份唯一的标识
public CustomToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
}
1.3 自定义异常
package com.wpj.exception;
/**
* 定义业务异常
*/
public class BusinessException extends RuntimeException {
private int messageCode;
private String defaultMessage;
public BusinessException(int messageCode, String defaultMessage){
super(defaultMessage);
this.messageCode = messageCode;
this.defaultMessage = defaultMessage;
}
public String getDefaultMessage() {
return defaultMessage;
}
public int getMessageCode() {
return messageCode;
}
}
1.4 定义常量类
package com.wpj.constant;
public class Constant {
public static final String REQ_TOKEN = "token";
}
1.5 自定义过滤器拦截请求
package com.wpj.filter;
import com.alibaba.fastjson.JSONObject;
import com.wpj.constant.Constant;
import com.wpj.exception.BusinessException;
import com.wpj.token.CustomToken;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* 对身份进行二次校验的时候就会进来这里
*/
public class CustomAccessControllerFilter extends AccessControlFilter {
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
HttpServletRequest request = (HttpServletRequest) servletRequest;
try {
// 校验身份
// 获取token
String token = request.getHeader(Constant.REQ_TOKEN);
// 判断token是否为空
if (StringUtils.isEmpty(token)) {
// 身份非法
throw new BusinessException(400001, "用户的请求token不能为空。");
} else {
// 带了token
// 封装token交给shiro去认证,查看是否合法
CustomToken customToken = new CustomToken(token);
// 用户第一次登陆并不会执行,是在认证成功之后访问其他资源的时候才执行
getSubject(servletRequest, servletResponse).login(customToken);
}
} catch (BusinessException e) {
// 返回json告诉前端出问题了
resultResponse(e.getMessageCode(), e.getDefaultMessage(), servletResponse);
} catch (AuthenticationException e) { // 校验没通过异常
// e.getCause() 返回当前异常实例
if(e.getCause() instanceof BusinessException) {
// 将异常的实例进行转换
BusinessException err = (BusinessException) e.getCause();
resultResponse(err.getMessageCode(), err.getDefaultMessage(), servletResponse);
} else {
// 执行这里就说明这个异常是shiro返回的
resultResponse(400001, "用户认证失败", servletResponse);
}
} catch (AuthorizationException e) {
if(e.getCause() instanceof BusinessException) {
// 将异常的实例进行转换
BusinessException err = (BusinessException) e.getCause();
resultResponse(err.getMessageCode(), err.getDefaultMessage(), servletResponse);
} else {
// 执行这里就说明这个异常是shiro返回的
resultResponse(403001, "用户没有访问权限", servletResponse);
}
} catch (Exception e) { // 一些未考虑的异常
if(e.getCause() instanceof BusinessException) {
// 将异常的实例进行转换
BusinessException err = (BusinessException) e.getCause();
resultResponse(err.getMessageCode(), err.getDefaultMessage(), servletResponse);
} else {
// 执行这里就说明这个异常是shiro返回的
resultResponse(500001, "系统出现异常", servletResponse);
}
}
return false;
}
/**
* 告诉前端出错的信息
* @param messageCode
* @param defaultMessage
* @param response
*/
private void resultResponse(int messageCode, String defaultMessage, ServletResponse response) {
// 构建返回的数据
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", messageCode);
jsonObject.put("msg", defaultMessage);
// 设置下返回类型
response.setContentType(MediaType.APPLICATION_JSON_UTF8.toString());
// 获取输出流
try {
ServletOutputStream out = response.getOutputStream();
out.write(jsonObject.toJSONString().getBytes());
out.flush();
}catch (IOException e) {
e.printStackTrace();
}
}
}
1.6 自定义HashedCredentialsMatcher认证
package com.wpj.filter;
import com.wpj.exception.BusinessException;
import com.wpj.token.CustomToken;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
public class CustomHashedCredentialsMatcher extends HashedCredentialsMatcher {
/**
* 认证成功与否
* @param token
* @param info
* @return
*/
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
// 把前面传递的token取出来,再把存储导服务器的token取出来作比较
// 如果一致就返回true,否则就返回false
CustomToken customToken = (CustomToken) token;
// 取出principal的值
String tokenVal = (String) customToken.getPrincipal();
// 从Redis或者数据库,session取出信息来
// 模拟取出来了
String tokenServlet = "wpj";
// 进行比较
if(!(tokenVal.equals(tokenServlet))) {
// 如果不一致
throw new BusinessException(401000, "授权信息无效,请重新登录");
}
return true;
}
}