Springboot项目集成Shiro+JWT,同时集成swagger2便于测试
之前只是单独使用过Shiro和自定义token,后来觉得使用jwt更方便,所以集成做个笔记,直接按照步骤即插即用
1. 配置Maven
org.apache.shiro
shiro-core
1.3.2
org.apache.shiro
shiro-spring
1.3.2
com.auth0
java-jwt
3.4.0
2. 创建JWTToken用于保存token
/**
*
* 自定义Token
*
*
* @author LiGuangYao
* @data 2019/4/24 15:50
*/
public class JWTToken implements AuthenticationToken {
// 密钥
private String token;
public JWTToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
3. 创建JWT工具类
/**
*
* JWT工具类
*
*
* @author LiGuangYao
* @data 2019/4/24 15:51
*/
public class JWTUtils {
// 过期时间24
private static final long EXPIRE_TIME = 60 * 24 * 60 * 1000;
/**
* 校验token是否正确
*
* @param token 密钥
* @param username 登录名
* @param password 密码
* @return
*/
public static boolean verify(String token, String username, String password) {
try {
Algorithm algorithm = Algorithm.HMAC256(password);
JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 获取登录名
*
* @param token
* @return
*/
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 生成签名
*
* @param username
* @param password
* @return
*/
public static String sign(String username, String password) {
try {
// 指定过期时间
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(password);
return JWT.create()
.withClaim("username", username)
.withExpiresAt(date)
.sign(algorithm);
} catch (Exception e) {
return null;
}
}
}
4.自定义Shiro中的Realm
/**
*
* 自定义realm
*
*
* @author LiGuangYao
* @data 2019/4/24 15:55
*/
public class CustomRealm extends AuthorizingRealm {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private ShiroAuthService shiroAuthService;
/**
* 权限验证
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("doGetAuthorizationInfo+" + principals.toString());
String username = JWTUtils.getUsername(principals.toString());
User user = shiroAuthService.queryUserByName(username);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
List userPermissions = shiroAuthService.getPermissions(user.getUser_id());
// 基于Permission的权限信息
info.addStringPermissions(userPermissions);
return info;
}
/**
* 使用JWT替代原生Token
*
* @param token
* @return
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JWTToken;
}
/**
* 登录验证
* @param authcToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
String token = (String) authcToken.getCredentials();
String username = JWTUtils.getUsername(token);
User user = shiroAuthService.queryUserByName(username);
// 用户不会空
if (user != null) {
// 判断是否禁用
if (StringUtils.isBlank(UserForbidden.PROHIBIT.getMessage())) {
throw new DisabledAccountException(BizException.USER_LOCK);
}
// 密码验证
if (!JWTUtils.verify(token, username, user.getPassword())) {
throw new UnknownAccountException(BizException.USER_PASSWORD_EXISTENT);
}
return new SimpleAuthenticationInfo(token, token,Constant.CUSTOMREALM_NAME);
} else {
throw new UnknownAccountException(BizException.USER_EXISTENT);
}
}
}
5. 自定义Shrio中的Filter
/**
*
* 自定义过滤器
*
*
* @author LiGuangYao
* @data 2019/4/24 15:53
*/
public class JWTFilter extends AuthenticatingFilter {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token
String token = getRequestToken((HttpServletRequest) request);
if(StringUtils.isBlank(token)){
return null;
}
return new JWTToken(token);
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token,如果token不存在,直接返回401
String token = getRequestToken((HttpServletRequest) request);
if(StringUtils.isBlank(token)){
HttpServletResponse httpResponse = (HttpServletResponse) response;
String json = new Gson().toJson(
//自定义异常 需要自己创建或者填写
R.error(BizException.TOKEN_EXISTENT));
httpResponse.setContentType(Constant.RESPONSE_CONTENTTYPE);
httpResponse.getWriter().print(json);
return false;
}
return executeLogin(request, response);
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType(Constant.RESPONSE_CONTENTTYPE);
try {
//处理登录失败的异常
Throwable throwable = e.getCause() == null ? e : e.getCause();
R r = R.error(org.apache.http.HttpStatus.SC_UNAUTHORIZED, throwable.getMessage());
String json = new Gson().toJson(r);
httpResponse.getWriter().print(json);
} catch (IOException e1) {
}
return false;
}
/**
* 获取请求的token
*/
private String getRequestToken(HttpServletRequest httpRequest){
//从header中获取token
String token = httpRequest.getHeader(Constant.LOGIN_SIGN);
//如果header中不存在token,则从参数中获取token
if(StringUtils.isBlank(token)){
token = httpRequest.getParameter(Constant.LOGIN_SIGN);
}
return token;
}
}
6.自定义Shiro核心配置
/**
*
* Shiro核心配置
*
*
* @author LiGuangYao
* @data 2019/4/25 13:30
*/
@Configuration
public class ShiroConfig {
/**
* 先走 filter ,然后 filter 如果检测到请求头存在 token,则用 token 去 login,走 Realm 去验证
*
* @param securityManager the security manager
* @return the shiro filter factory bean
*/
@Bean
public ShiroFilterFactoryBean factory(SecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
Map filterMap = new HashMap<>();
//设置我们自定义的JWT过滤器
filterMap.put("jwt", new JWTFilter());
factoryBean.setFilters(filterMap);
factoryBean.setSecurityManager(securityManager);
// 设置无权限时跳转的 url;
factoryBean.setUnauthorizedUrl("/unauthorized/无权限");
Map filterRuleMap = new HashMap<>();
//访问/login和/unauthorized 不需要经过过滤器
filterRuleMap.put("/login", "anon");
filterRuleMap.put("/entry", "anon");
filterRuleMap.put("/unauthorized/**", "anon");
//swagger配置放行
filterRuleMap.put("/swagger-ui.html","anon");
filterRuleMap.put("/swagger/**","anon");
filterRuleMap.put("/webjars/**","anon");
filterRuleMap.put("/swagger-resources/**","anon");
filterRuleMap.put("/v2/**","anon");
//静态资源放行
filterRuleMap.put("/**/*.html","anon");
filterRuleMap.put("/**/*.jpg","anon");
filterRuleMap.put("/**/*.png","anon");
// 所有请求通过我们自己的JWT Filter
filterRuleMap.put("/**", "jwt");
// 访问 /unauthorized/** 不通过JWTFilter
factoryBean.setFilterChainDefinitionMap(filterRuleMap);
return factoryBean;
}
/**
* 注入 securityManager
*
* @return the security manager
*/
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置自定义 realm.
securityManager.setRealm(customRealm());
/*
* 关闭shiro自带的session,详情见文档
* http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
*/
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
return securityManager;
}
@Bean
public CustomRealm customRealm() {
return new CustomRealm();
}
/**
* 开启shiro aop注解支持. 使用代理方式; 所以需要开启代码支持;
*
* @param securityManager 安全管理器
* @return 授权Advisor
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
7.集成配置Swagger2 配置Maven
io.springfox
springfox-swagger2
2.9.2
io.springfox
springfox-swagger-ui
2.9.2
io.swagger
swagger-annotations
1.5.21
io.swagger
swagger-models
1.5.21
8.Swagger2中的yml加载project配置
project:
package: //所属包名
name: //swagger2展示名称
version: 1.0.0 //版本号
author:
email:
site:
9.Swagger2核心配置
/**
*
* Swagger2配置类
* 在与spring boot集成时,放在与Application.java同级的目录下。同时通过资源文件加载的方式便于修改
* 通过@Configuration注解,让Spring来加载该类配置。
* 再通过@EnableSwagger2注解来启用Swagger2。
*
*
* @author LiGuangYao
* @data 2019/4/8 14:19
*/
@Configuration
@EnableSwagger2
@ConditionalOnProperty(prefix = "swagger2",value = {"enable"},havingValue = "true")
public class Swagger2 {
@Value("${project.package}")
String projectPackage;
@Value("${project.name}")
String projectName;
@Value("${project.version}")
String projectVersion;
@Value("${project.author}")
String projectAuthor;
@Value("${project.email}")
String projectEmail;
@Value("${project.site}")
String projectSite;
/**
* 创建API应用
* apiInfo() 增加API相关信息
* 通过select()函数返回一个ApiSelectorBuilder实例,用来控制哪些接口暴露给Swagger来展现,
* 本例采用指定扫描的包路径来定义指定要建立API的目录。
*
* @return
*/
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage(String.format("%s.controller", projectPackage)))
.paths(PathSelectors.any())
.build()
.securitySchemes(securitySchemes())
.securityContexts(securityContexts());
}
private static List securitySchemes() {
List apiKeyList= new ArrayList();//x-auth-token
//Constant.LOGIN_SIGN 为请求头中自定义的token名称
apiKeyList.add(new ApiKey(Constant.LOGIN_SIGN, Constant.LOGIN_SIGN, "header"));
return apiKeyList;
}
private List securityContexts() {
List securityContexts=new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.regex("^(?!auth).*$"))//^(?!auth).*$
.build());
return securityContexts;
}
List defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List securityReferences=new ArrayList<>();
securityReferences.add(new SecurityReference(Constant.LOGIN_SIGN, authorizationScopes));
return securityReferences;
}
/**
* 创建该API的基本信息(这些基本信息会展现在文档页面中)
* 访问地址:http://项目实际地址/swagger-ui.html
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title(String.format("%s API Documents", projectName))
.description("API Docs")
.contact(new Contact(projectAuthor, projectSite, projectEmail))
.version(projectVersion)
.build();
}
}