与 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: localhostport: 6379password:timeout: 2000slettuce: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();//效验TOKENDecodedJWT 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;}@Overridepublic Object getPrincipal() {return token;}@Overridepublic Object getCredentials() {return token;}}
⑤:重写 filter
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter implements Filter {// 对跨域提供支持@Overrideprotected 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);}// 执行登录认证@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {try {return executeLogin(request, response);} catch (Exception e) {log.error("JwtFilter过滤验证失败!");return false;}}// 执行登录@Overrideprotected 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);// 如果没有抛出异常则代表登入成功,返回truereturn true;} catch (AuthenticationException e) {return false;}}// 认证失败时,自定义返回json数据@Overrideprotected 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);//设置拦截器链////设置拦截链mapLinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();//放行请求filterChainDefinitionMap.put("/shiro/getToken", "anon");//自定义过滤器//// 添加自己的自定义拦截器并且取名为jwtMap<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;}// 安全管理器@Beanpublic 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;}@Beanpublic Realm shiroRealm(){return new ShiroRealm();}// 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)@Beanpublic DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();advisorAutoProxyCreator.setProxyTargetClass(true);return advisorAutoProxyCreator;}// 开启aop注解支持@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}}
⑦:shiro 认证、授权类
@Slf4j
public class ShiroRealm extends AuthorizingRealm {@Autowiredprivate RedisTemplate redisTemplate;// 设置对应的token类型@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof JwtToken;}@Overrideprotected 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;}@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {log.info("开始身份认证.....................");//获取tokenString 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());}// 刷新tokenpublic 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 {@Autowiredprivate 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 测试