参考: 一个安全框架,但不只是一个安全框架。它能实现多种多样的功能。并不只是局限在web层。在国内的市场份额占比高于SpringSecurity,是使用最多的安全框架可以实现用户的认证和授权。比SpringSecurity要简单的多。 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。 maven依赖 JwtOperator 定义token的相关操作,包括 token生成、认证和解析 JwtOperatorHandle 在 token 的 claim 中存储 用户信息的json串和类信息,用于反序列化bean Realm 作为 shiro 认证的主要处理,包括 认证 和 鉴权 filter 包括 LoginFilter 和 JWTFilter,用于过滤 登录请求和普通请求 处理登录请求,校验用户的登录信息,返回登录用户的基本信息和token LoginService 定义处理用户登录的逻辑,比如密码是否正常,用户的状态等 JwtFilter 处理普通请求,校验token有效性,从token中获取用户基本信息。 ShiroProperties shiro 相关的配置bean ShiroConfig shiro的自定义配置,包括realm、filter、securityManager等的注入 loginService 这里只简单实现,具体可进行覆盖,给当前用户赋予 api:u2 权限 JwtSubjectFactory 为自定义的SubjectFactory,主要是禁止shiro创建session,因为要使用无状态的token就行认证鉴权 AuthorizationAttributeSourceAdvisor 和 DefaultAdvisorAutoProxyCreator 为了创建 权限认证的增强,主要处理 controller 中的 各个 MappingHandler (RequestMapping 对应 method),AuthorizationAttributeSourceAdvisor 的 matches 方法用于判断RequestMapping 对应 method是否需要增强处理,此增强包括权限认证等。 AuthorizationAttributeSourceAdvisor – matches 异常处理 Controller定义 /api/u1 非鉴权接口,游客可访问 此时未携带 token 信息,可正常访问 携带token 此时携带 token 信息,可正常访问 /api/u2 具有api:u2权限的用户可访问(在默认实现的LoginService中已给用户赋权,有权限访问) 不携带token 此时未携带 token 信息,/api/u2 返回 401 没有权限访问 携带token 此时携带 token 信息,token拥有 api:u2 也就是当前接口权限,正常访问 /api/u3 具有api:u2权限的用户可访问,默认登录用户无权限访问 不携带token 此时未携带 token 信息,/api/u2 返回 401 没有权限访问 携带token 此时携带 token 信息,token未拥有 api:u3 也就是当前接口权限,没有权限访问
SpringBoot整合Shiro JWT
https://www.jianshu.com/p/9b6eb3308294
https://blog.csdn.net/bicheng4769/article/details/866682091:概述
1.1、shiro
基于session进行会话管理
本文整合 JWT 后,需关闭原 shiro 的 session1.2、JWT
JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
本文使用JWT作为无状态请求依据。1.3、整合思路
2:SpringBoot + Shiro + JWT
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency>
2.1、JWT 工具
public interface JwtOperator { String token(TokenUser user); boolean verify(String token); TokenUser parse(String token); }
class JwtOperatorHandle implements JwtOperator { private static final String ENCODE_SECRET_KEY = "yyl-4012-secret-key"; private static final String USER_VALUE_KEY = "user-value-key"; private static final String USER_CLASS_KEY = "user-class-key"; private SignatureAlgorithm algorithm = SignatureAlgorithm.HS256; private byte[] base64EncodeSecretKey; public JwtOperatorHandle() { base64EncodeSecretKey = Base64.getEncoder().encode(ENCODE_SECRET_KEY.getBytes()); } public String token(TokenUser user) { Date now = new Date(); String token = Jwts.builder() .claim(USER_VALUE_KEY, JSON.toJSONString(user)) .claim(USER_CLASS_KEY, user.getClass()) .setIssuedAt(now) .setExpiration(new Date( now.getTime() + user.getExpirationMinutes() * 60 * 1000 ) ) .signWith(algorithm, base64EncodeSecretKey) .compact(); return token; } public boolean verify(String token) { try { Jwts.parser().setSigningKey(base64EncodeSecretKey).parse(token); } catch (ExpiredJwtException e) { return false; } return true; } public TokenUser parse(String token) { Claims claims; try { claims = (Claims) Jwts.parser() .setSigningKey(base64EncodeSecretKey) .parse(token) .getBody(); } catch (ExpiredJwtException e) { throw new TokenExpiredException(); } String userJson = claims.get(USER_VALUE_KEY, String.class); String clazzString = claims.get(USER_CLASS_KEY, String.class); Class<TokenUser> clazz; try { clazz = (Class) Class.forName(clazzString); } catch (Exception e) { throw new RuntimeException(e); } TokenUser tokenUser = JSONObject.parseObject(userJson, clazz); return tokenUser; } }
2.2、CustomRealm
public class CustomRealm extends AuthorizingRealm { @Override public boolean supports(AuthenticationToken token) { return token instanceof JwtToken; } /** * 这里执行鉴权过程 * 对于配置了需要鉴权url,执行此方法 * * @param principals * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String ticket = (String) principals.getPrimaryPrincipal(); JwtOperator operator = JwtOperatorBuilder.build(); ShiroUserDetail userDetail = (ShiroUserDetail) operator.parse(ticket); List<String> permissions = userDetail.getPermissions(); // 获取当前用户信息 // 判断权限 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setStringPermissions(permissions == null ? null : permissions.stream().collect(Collectors.toSet())); return info; } /** * 获取认证信息 * 如判断用户存不存在 * 用户密码是否正确等 * * @param token * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { return new SimpleAuthenticationInfo(token.getPrincipal(), token.getCredentials(), "realmName"); } }
2.3、Shiro Filter
LoginFilter
public class LoginFilter extends AccessControlFilter { private LoginService loginService; public LoginFilter(LoginService loginService) { this.loginService = loginService; } @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { return false; } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest servletRequest = (HttpServletRequest) request; String username = servletRequest.getParameter("username"); String password = servletRequest.getParameter("password"); ShiroLoginUser loginUser = new ShiroLoginUser(username, password); ShiroUserDetail userDetail = loginService.login(loginUser); JwtOperator jwtOperator = JwtOperatorBuilder.build(); String token = jwtOperator.token(userDetail); Map<String, Object> map = new HashMap<>(); map.put("token", token); map.put("user", userDetail); HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setContentType("application/json"); response.getWriter().write(JSONObject.toJSONString(map)); return false; } }
public interface LoginService { ShiroUserDetail login(ShiroLoginUser loginUser); }
这里只在onAccessDenied方法中进行业务处理。
用户携带的token信息位于headers 的 Authorization 中public class JwtFilter extends AccessControlFilter { private static final String TOKEN_HEADER = "Authorization"; /* * 1. 返回true,shiro 就直接允许访问url * 2. 返回false,shiro 才会根据 onAccessDenied 的方法的返回值决定是否允许访问url * */ @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { // 这里直接返回 false ,让所有的有权限校验接口在 onAccessDenied 方法中处理 return false; } /** * 返回结果为true表明登录通过 */ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { // HttpServletResponse httpResponse = (HttpServletResponse) response; HttpServletRequest servletRequest = (HttpServletRequest) request; String token = servletRequest.getHeader(TOKEN_HEADER); // 未携带 token 时,交给 shiro 判断 是否为放开权限接口(游客可访问接口) // 如果 当前接口需要权限访问,而又未携带 token ,shiro 会抛出 AuthorizationException,需处理此异常 // 如果不返回 true ,则所有的 url 都需要携带 token 进行权限校验 if (token == null || "".equals(token)){ return true; } // 验证 token 的有效性 JwtToken jwtToken = new JwtToken(token); try { // 权限认证 getSubject(request, response).login(jwtToken); }catch (Exception e){ e.printStackTrace(); httpResponse.setStatus(401); httpResponse.getWriter().write("access denied"); return false; } return true; } }
@ConfigurationProperties(prefix = "system.shiro") @Data public class ShiroProperties { private String login; private String logout; }
@Configuration @EnableConfigurationProperties(ShiroProperties.class) public class ShiroConfig { private ShiroProperties properties; public ShiroConfig(ShiroProperties properties) { this.properties = properties; } @Bean public CustomRealm customRealm() { return new CustomRealm(); } @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager, LoginService loginService) { ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); factoryBean.setLoginUrl(properties.getLogin()); factoryBean.setSecurityManager(securityManager); // 注册jwt拦截器 Map<String, Filter> filterMap = new HashMap<>(); filterMap.put("jwt", new JwtFilter()); filterMap.put("login", new LoginFilter(loginService)); factoryBean.setFilters(filterMap); // 设置 url 对应 filter Map<String, String> filterChainDefinitionMap = new HashMap<>(); filterChainDefinitionMap.put("/**", "jwt"); filterChainDefinitionMap.put("/login", "login"); factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return factoryBean; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(customRealm()); // 关闭 shiro 的session DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator(); sessionStorageEvaluator.setSessionStorageEnabled(false); subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator); securityManager.setSubjectDAO(subjectDAO); securityManager.setSubjectFactory(new JwtSubjectFactory()); return securityManager; } /** * 配置shiro对于权限注解的支持 * * DefaultAdvisorAutoProxyCreator 实现了 BeanProcessor * 当 ApplicationContext读如所有的Bean配置信息后,这个类将扫描上下文, * 寻找所有的Advistor(一个Advisor是一个切入点和一个通知的组成),将这些 Advisor 应用到所有符合切入点的Bean中 * * @return */ @Bean public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); // 使用 CGLIB 方式创建代理对象 advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } /** * 配置shiro框架提供的切面类,用于创建代理对象 * 这里为创建 权限校验的 advisor * 在 DefaultAdvisorAutoProxyCreator 的作用下生效 * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){ return new AuthorizationAttributeSourceAdvisor(); } @Bean @ConditionalOnMissingBean public LoginService loginService(){ return loginUser -> { ShiroUserDetail shiroUserDetail = new ShiroUserDetail(); shiroUserDetail.setUsername(loginUser.getUsername()); List<String> permissions = new ArrayList<>(); permissions.add("api:u2"); shiroUserDetail.setPermissions(permissions); return shiroUserDetail; }; } class JwtSubjectFactory extends DefaultWebSubjectFactory { @Override public Subject createSubject(SubjectContext context) { // 这里禁止创建session context.setSessionCreationEnabled(false); return super.createSubject(context); } } }
public boolean matches(Method method, Class targetClass) { Method m = method; if ( isAuthzAnnotationPresent(m) ) { return true; } //The 'method' parameter could be from an interface that doesn't have the annotation. //Check to see if the implementation has it. if ( targetClass != null) { try { m = targetClass.getMethod(m.getName(), m.getParameterTypes()); return isAuthzAnnotationPresent(m) || isAuthzAnnotationPresent(targetClass); } catch (NoSuchMethodException ignored) { //default return value is false. If we can't find the method, then obviously //there is no annotation, so just use the default return value. } } return false; }
private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES = new Class[] { RequiresPermissions.class, RequiresRoles.class, RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class }; private boolean isAuthzAnnotationPresent(Method method) { for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) { Annotation a = AnnotationUtils.findAnnotation(method, annClass); if ( a != null ) { return true; } } return false; }
@ControllerAdvice public class ShiroExceptionHandler { @ExceptionHandler(AuthorizationException.class) @ResponseBody public String notAuthorized(HttpServletResponse response){ response.setStatus(401); return "你没有权限"; } @ExceptionHandler(TokenExpiredException.class) @ResponseBody public String tokenExpired(HttpServletResponse response){ response.setStatus(405); return "token 已失效,请重新登陆"; } }
3:测试
3.1、登录
3.1、普通接口访问
@GetMapping("/api/u1") @ResponseBody public String u1() { return "1"; } @RequiresPermissions("api:u2") @GetMapping("/api/u2") @ResponseBody public String u2() { return "2"; } @RequiresPermissions("api:u3") @GetMapping("/api/u3") @ResponseBody public String u3() { return "3"; }
不携带token
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算