与 Spring 集成:与 Spring 集成
与 SpringBoot 集成:与 SpringBoot 集成
1、SpringBoot + Shiro + Jwt
①:引入 pom.xml
:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
②:配置信息 yml:
spring:
redis:
host: localhost
port: 6379
password:
timeout: 2000s
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
③:JWT 工具类 JwtUtil
@Slf4j
public class JwtUtil {
private static final long EXPIRE_TIME = 30 * 60 * 1000;
public static String sign(String username, String secret) {
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(secret);
// 附带username信息
return JWT.create()
.withClaim("username", username)
.withExpiresAt(date)
.sign(algorithm);
}
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
public static boolean verify(String token, String username, String secret) {
try {
//根据密码生成JWT效验器
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("username", username)
.build();
//效验TOKEN
DecodedJWT jwt = verifier.verify(token);
log.info("登录验证成功!");
return true;
} catch (Exception exception) {
log.error("JwtUtil登录验证失败!");
return false;
}
}
}
④:重写 token
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;
}
}
⑤:重写 filter
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter implements Filter {
// 对跨域提供支持
@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请求,这里我们给option请求直接返回正常状态
if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
httpServletResponse.setStatus(HttpStatus.OK.value());
return false;
}
return super.preHandle(request, response);
}
// 执行登录认证
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
try {
return executeLogin(request, response);
} catch (Exception e) {
log.error("JwtFilter过滤验证失败!");
return false;
}
}
// 执行登录
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String token = httpServletRequest.getHeader("SevenHee-Token");
JwtToken jwtToken = new JwtToken(token);
// 提交给realm进行登入,如果错误他会抛出异常并被捕获
try {
getSubject(request, response).login(jwtToken);
// 如果没有抛出异常则代表登入成功,返回true
return true;
} catch (AuthenticationException e) {
return false;
}
}
// 认证失败时,自定义返回json数据
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
JSONObject jsonObject = new JSONObject();
jsonObject.put("msg", "认证失败");
Object parse = JSONObject.toJSON(jsonObject);
response.setCharacterEncoding("utf-8");
response.getWriter().print(parse);
return super.onAccessDenied(request, response);
}
}
⑥:shiro 配置类
@Configuration
public class ShiroConfig {
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
//创建拦截链实例
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//设置拦截器链//
//设置拦截链map
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//放行请求
filterChainDefinitionMap.put("/shiro/getToken", "anon");
//自定义过滤器//
// 添加自己的自定义拦截器并且取名为jwt
Map<String, Filter> filterMap = new HashMap<>(1);
filterMap.put("jwt", new JwtFilter());
shiroFilterFactoryBean.setFilters(filterMap);
//拦截链配置,从上向下顺序执行,一般将jwt过滤器放在最为下边
filterChainDefinitionMap.put("/**", "jwt");
//配置拦截链到过滤器工厂
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
//登录url、未授权url//
//设置组登录请求,其他路径一律自动跳转到这里
shiroFilterFactoryBean.setLoginUrl("/login");
//未授权跳转路径
shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
//返回实例
return shiroFilterFactoryBean;
}
// 安全管理器
@Bean
public DefaultWebSecurityManager securityManager(Realm shiroRealm) {
//创建默认的web安全管理器
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
//配置shiro的自定义认证逻辑
defaultSecurityManager.setRealm(shiroRealm);
/*
* 关闭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);
defaultSecurityManager.setSubjectDAO(subjectDAO);
//返回安全管理器实例
return defaultSecurityManager;
}
@Bean
public Realm shiroRealm(){
return new ShiroRealm();
}
// 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
// 开启aop注解支持
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
⑦:shiro 认证、授权类
@Slf4j
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private RedisTemplate redisTemplate;
// 设置对应的token类型
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//权限认证
log.info("开始进行权限认证.............");
//获取用户名
String token = (String) SecurityUtils.getSubject().getPrincipal();
String username = JwtUtil.getUsername(token);
//模拟数据库校验,写死用户名 test,其他用户无法登陆成功
if (!GlobalConstant.USER_NAME.equals(username)) {
return null;
}
//创建授权信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//创建set集合,存储权限
HashSet<String> rootSet = new HashSet<>();
//添加权限
rootSet.add("user:show");
rootSet.add("user:admin");
//设置权限
info.setStringPermissions(rootSet);
//返回权限实例
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("开始身份认证.....................");
//获取token
String token = (String) authenticationToken.getCredentials();
//创建字符串,存储用户信息
String username = null;
try {
//获取用户名
username = JwtUtil.getUsername(token);
} catch (AuthenticationException e) {
throw new AuthenticationException("heard的token拼写错误或者值为空");
}
if (username == null) {
throw new AuthenticationException("token无效");
}
// 校验token是否超时失效 & 或者账号密码是否错误
if (!jwtTokenRefresh(token, username, GlobalConstant.PASSWORD)) {
throw new AuthenticationException("Token失效,请重新登录!");
}
//返回身份认证信息
return new SimpleAuthenticationInfo(token, token, this.getName());
}
// 刷新token
public boolean jwtTokenRefresh(String token, String userName, String passWord) {
String redisToken = (String) redisTemplate.opsForValue().get(token);
if (redisToken != null) {
if (!JwtUtil.verify(redisToken, userName, passWord)) {
String newToken = JwtUtil.sign(userName, passWord);
//设置redis缓存
redisTemplate.opsForValue().set(token, newToken, GlobalConstant.REDIS_EXPIRE_TIME * 2 / 1000, TimeUnit.SECONDS);
}
return true;
}
return false;
}
}
⑧:常量类
public interface GlobalConstant {
// redis 过期时间
Integer REDIS_EXPIRE_TIME = 1800000;
// 测试用户
String USER_NAME = "test";
// 测试密码
String PASSWORD = "123456";
}
⑨:测试接口
@RestController
@RequestMapping("/shiro")
public class ShiroController {
@Autowired
private RedisTemplate redisTemplate;
@RequestMapping("/getToken")
public String getToken() {
String token = JwtUtil.sign(GlobalConstant.USER_NAME, GlobalConstant.PASSWORD);
redisTemplate.opsForValue().set(token, token, GlobalConstant.REDIS_EXPIRE_TIME * 2 / 1000, TimeUnit.SECONDS);
return token;
}
@RequiresPermissions("user:admin")
@RequestMapping("/test")
public String test() {
System.out.println("进入测试,只有带有令牌才可以进入该方法");
return "访问接口成功";
}
}
⑩:postman 测试