This article introduces the method of Spring-boot combined with Shrio to implement JWT, and shares it with you, as follows:
Regarding verification, there are roughly two aspects:
Main solution: Use a custom Shiro Filter
Project construction:
This is a spring-boot web project. If you don’t know about spring-boot project construction, please google.
pom.mx introduces related jar packages
<!-- shiro permission management--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>${shiro.version}</version> </dependency> <!-- JWT --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> Shrio related configuration
Make the point! ! Customized a Filter
filterMap.put("JWTFilter", new JWTFilter()); @Configurationpublic class ShiroConfig { @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); // Add your own filter and name it JWTFilter Map<String, Filter> filterMap = new HashMap<>(); filterMap.put("JWTFilter", new JWTFilter()); shiroFilterFactoryBean.setFilters(filterMap); /* * Custom url rules* http://shiro.apache.org/web.html#urls- */ Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap(); filterChainDefinitionMap.put("/**", "JWTFilter"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * securityManager does not need to inject shiroDBRealm directly, which may cause transaction failure* For the solution, see handleContextRefresh * http://www.debugrun.com/a/NKS9EJQ.html */ @Bean("securityManager") public DefaultWebSecurityManager securityManager(TokenRealm tokenRealm) { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(tokenRealm); /* * Close the session that comes with shiro, see the documentation for details* 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); manager.setSubjectDAO(subjectDAO); return manager; } @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean(name = "TokenRealm") @DependsOn("lifecycleBeanPostProcessor") public TokenRealm tokenRealm() { return new TokenRealm(); } @Bean @DependsOn("lifecycleBeanPostProcessor") public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); // Force cglib to prevent duplicate proxy and possible proxy errors// https://zhuanlan.zhihu.com/p/29161098 defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return new AuthorizationAttributeSourceAdvisor(); }} Customize Shrio filter
Execution order: preHandle -> doFilterInternal -> executeLogin -> onLoginSuccess
The main judgment is whether the login request is doFilterInternal
public class JWTFilter extends BasicHttpAuthenticationFilter { /** * Customize the method to execute login*/ @Override protected boolean executeLogin(ServletRequest request, ServletResponse response) throws IOException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; UsernamePasswordToken usernamePasswordToken = JSON.parseObject(httpServletRequest.getInputStream(), UsernamePasswordToken.class); // Submit it to realm for login. If the error is wrong, it will throw an exception and be caught Subject subject = this.getSubject(request, response); subject.login(usernamePasswordToken); return this.onLoginSuccess(usernamePasswordToken, subject, request, response); // Error throw an exception} /** * First method to execute*/ @Override protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { return super.preHandle(request, response); } /** * Login operation after successful login* Add jwt's header */ @Override protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) { HttpServletResponse httpServletResponse = (HttpServletResponse) response; String jwtToken = Jwts.builder() .setId(token.getPrincipal().toString()) .setExpiration(DateTime.now().plusMinutes(30).toDate()) .signWith(SignatureAlgorithm.HS256, JWTCost.signatureKey) .compact(); httpServletResponse.addHeader(AUTHORIZATION_HEADER, jwtToken); return true; } /** * The main process of login and verification* Determine whether it is login, or an ordinary request after login*/ @Override public void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse; String servletPath = httpServletRequest.getServletPath(); if (StringUtils.equals(servletPath, "/login")) { //ExecuteLogin(servletRequest, servletResponse); } else { String authenticationHeader = httpServletRequest.getHeader(AUTHORIZATION_HEADER); if (StringUtils.isNotEmpty(authenticationHeader)) { Claims body = Jwts.parser() .setSigningKey(JWTCost.signatureKey) .parseClaimsJws(authenticationHeader) .getBody(); if (body != null) { //Update token body.setExpiration(DateTime.now().plusMinutes(30).toDate()); String updateToken = Jwts.builder().setClaims(body).compact(); httpServletResponse.addHeader(AUTHORIZATION_HEADER, updateToken); //Add user credentials PrincipalCollection principals = new SimplePrincipalCollection(body.getId(), JWTCost.UserNamePasswordRealm);//Assemble shiro user information WebSubject.Builder builder = new WebSubject.Builder(servletRequest, servletResponse); builder.principals(principals); builder.authenticated(true); builder.sessionCreationEnabled(false); WebSubject subject = builder.buildWebSubject(); //Put into the container and call ThreadContext.bind(subject); filterChain.doFilter(httpServletRequest, httpServletResponse); } } else { httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value()); } } }} Login failed processing
Handle Shrio exceptions
@RestControllerAdvicepublic class GlobalControllerExceptionHandler { @ExceptionHandler(value = Exception.class) public Object allExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) { String message = exception.getCause().getMessage(); LogUtil.error(message); return new ResultInfo(exception.getClass().getName(), message); } /*=========== Shiro Exception Intercept=========================*/ @ExceptionHandler(value = IncorrectCredentialsException.class) public String IncorrectCredentialsException(HttpServletRequest request, HttpServletResponse response, Exception exception) { response.setStatus(HttpStatus.FORBIDDEN.value()); return "IncorrectCredentialsException"; } @ExceptionHandler(value = UnknownAccountException.class) public String UnknownAccountException(HttpServletRequest request, HttpServletResponse response, Exception exception) { response.setStatus(HttpStatus.FORBIDDEN.value()); return "UnknownAccountException"; } @ExceptionHandler(value = LockedAccountException.class) public String LockedAccountException(HttpServletRequest request, HttpServletResponse response, Exception exception) { response.setStatus(HttpStatus.FORBIDDEN.value()); return "LockedAccountException"; } @ExceptionHandler(value = ExcessiveAttemptsException.class) public String ExcessiveAttemptsException(HttpServletRequest request, HttpServletResponse response, Exception exception) { response.setStatus(HttpStatus.FORBIDDEN.value()); return "ExcessiveAttemptsException"; } @ExceptionHandler(value = AuthenticationException.class) public String AuthenticationException(HttpServletRequest request, HttpServletResponse response, Exception exception) { response.setStatus(HttpStatus.FORBIDDEN.value()); return "AuthenticationException"; } @ExceptionHandler(value = UnauthorizedException.class) public String UnauthorizedException(HttpServletRequest request, HttpServletResponse response, Exception exception) { response.setStatus(HttpStatus.FORBIDDEN.value()); return "UnauthorizedException"; }}Handling JWT exceptions
This is a pitfall, because it is an exception that occurs in the filter, and @ExceptionHandler cannot intercept it.
/** * Intercept spring boot Error page*/@RestControllerpublic class GlobalExceptionHandler implements ErrorController { @Override public String getErrorPath() { return "/error"; } @RequestMapping(value = "/error") public Object error(HttpServletRequest request, HttpServletResponse response) throws Exception { // Error handling logic Exception exception = (Exception) request.getAttribute("javax.servlet.error.exception"); Throwable cause = exception.getCause(); if (cause instanceof ExpiredJwtException) { response.setStatus(HttpStatus.GATEWAY_TIMEOUT.value()); return new ResultInfo("ExpiredJwtException", cause.getMessage()); } if (cause instanceof MalformedJwtException) { response.setStatus(HttpStatus.FORBIDDEN.value()); return new ResultInfo("MalformedJwtException", cause.getMessage()); } return new ResultInfo(cause.getCause().getMessage(), cause.getMessage()); }}Regarding authorization information such as permissions, you can directly put it in Redis to cache. I think it's good, too.
Source code presents: githup-shiro branch: Warm reminder: The test code may be messy in daily life.
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.