SpringBoot2.0+Shiro+JWT 整合

SpringBoot2.0+Shiro+JWT 整合

JSON Web Token(JWT)是一個很是輕巧的規範。這個規範容許咱們使用 JWT 在用戶和服務器之間傳遞安全可靠的信息。 咱們利用必定的編碼生成 Token,並在 Token 中加入一些非敏感信息,將其傳遞。html

安裝環境

開發工具:STS
Maven版本:apache-maven-3.5.2
java jdk 1.8
MySQL版本:5.7
系統:Windows10

一.新建Maven項目

配置Maven項目的pom.xml文件java

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>cn.codepeople</groupId> <artifactId>SpringBoot-Shiro-JWT</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <log4j.version>1.2.17</log4j.version> <shiro.version>1.4.0</shiro.version> <com.alibaba.druid.version>1.1.10</com.alibaba.druid.version> <mysql.version>5.1.47</mysql.version> <mybatis.version>2.0.0</mybatis.version> <jwt.version>3.8.0</jwt.version> <swagger2.version>2.8.0</swagger2.version> </properties> <dependencies> <!-- spring boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-log4j2 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> <!-- 引入shiro的spring整合框架 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>${com.alibaba.druid.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <!-- Apache Commons --> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.2</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.54</version> </dependency> <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>${jwt.version}</version> </dependency> <!-- swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${swagger2.version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${swagger2.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> <!-- 打包時拷貝MyBatis的映射文件 --> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.*</include> </includes> <filtering>true</filtering> </resource> </resources> </build> </project> 

二.準備shiro和jwt工具和相關類

JWTFilter.javamysql

/** * @Title: JWTFilter.java * @Package www.codepeople.cn.shiro * @Description: * Copyright: Copyright (c) 2019 www.codepeople.cn Inc. All rights reserved. * Website: www.codepeople.cn * 注意:本內容僅限於海南科瀾技術信息有限公司內部傳閱,禁止外泄以及用於其餘的商業目 * @Author 劉仁 * @DateTime 2019年4月1日 下午4:52:19 * @version V1.0 */ package www.codepeople.cn.shiro; import java.io.IOException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.RequestMethod; import lombok.extern.slf4j.Slf4j; /** * @ClassName: JWTFilter * @Description: * @Author 劉仁 * @DateTime 2019年4月1日 下午4:52:19 */ @Slf4j public class JWTFilter extends BasicHttpAuthenticationFilter{ /** * 判斷用戶是否想要登入。 * 檢測header裏面是否包含Authorization字段便可 */ @Override protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) { HttpServletRequest req = (HttpServletRequest) request; String authorization = req.getHeader("Authorization"); log.info("判斷用戶是否想要登陸:{}",authorization); return authorization != null; } /** * */ @Override protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String authorization = httpServletRequest.getHeader("Authorization"); log.info("判斷用戶是否想要登陸x:{}",authorization); JWTToken token = new JWTToken(authorization); // 提交給realm進行登入,若是錯誤他會拋出異常並被捕獲 getSubject(request, response).login(token); // 若是沒有拋出異常則表明登入成功,返回true return true; } /** * 這裏咱們詳細說明下爲何最終返回的都是true,即容許訪問 * 例如咱們提供一個地址 GET /article * 登入用戶和遊客看到的內容是不一樣的 * 若是在這裏返回了false,請求會被直接攔截,用戶看不到任何東西 * 因此咱們在這裏返回true,Controller中能夠經過 subject.isAuthenticated() 來判斷用戶是否登入 * 若是有些資源只有登入用戶才能訪問,咱們只須要在方法上面加上 @RequiresAuthentication 註解便可 * 可是這樣作有一個缺點,就是不可以對GET,POST等請求進行分別過濾鑑權(由於咱們重寫了官方的方法),但實際上對應用影響不大 */ @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { if (isLoginAttempt(request, response)) { try { executeLogin(request, response); } catch (Exception e) { response401(request, response); } } return true; } /** * 對跨域提供支持 */ @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); } /** * 將非法請求跳轉到 /401 */ private void response401(ServletRequest req, ServletResponse resp) { try { HttpServletResponse httpServletResponse = (HttpServletResponse) resp; httpServletResponse.sendRedirect("/401"); } catch (IOException e) { log.error(e.getMessage()); } } } 

JWTToken.javagit

/** * @Title: JWTToken.java * @Package www.codepeople.cn.shiro * @Description: * Copyright: Copyright (c) 2019 www.codepeople.cn Inc. All rights reserved. * Website: www.codepeople.cn * 注意:本內容僅限於海南科瀾技術信息有限公司內部傳閱,禁止外泄以及用於其餘的商業目 * @Author 劉仁 * @DateTime 2019年4月1日 下午4:49:43 * @version V1.0 */ package www.codepeople.cn.shiro; import org.apache.shiro.authc.AuthenticationToken; /** * @ClassName: JWTToken * @Description: * @Author 劉仁 * @DateTime 2019年4月1日 下午4:49:43 */ public class JWTToken implements AuthenticationToken { private static final long serialVersionUID = 1L; // 祕鑰 private String token; public JWTToken(String token) { this.token = token; } @Override public Object getPrincipal() { return token; } @Override public Object getCredentials() { return token; } } 

MyRealm.javaweb

/** * @Title: MyRealm.java * @Package www.codepeople.cn.shiro * @Description: * Copyright: Copyright (c) 2019 www.codepeople.cn Inc. All rights reserved. * Website: www.codepeople.cn * 注意:本內容僅限於海南科瀾技術信息有限公司內部傳閱,禁止外泄以及用於其餘的商業目 * @Author 劉仁 * @DateTime 2019年4月1日 下午4:31:33 * @version V1.0 */ package www.codepeople.cn.shiro; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import www.codepeople.cn.entity.User; import www.codepeople.cn.service.UserService; import www.codepeople.cn.util.JWTUtil; /** * @ClassName: MyRealm * @Description: * @Author 劉仁 * @DateTime 2019年4月1日 下午4:31:33 */ public class MyRealm extends AuthorizingRealm { @Autowired private UserService userService; @Override public boolean supports(AuthenticationToken token) { return token instanceof JWTToken; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = JWTUtil.getUsername(principals.toString()); User user = userService.getUser(username); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.addRole(user.getRole()); Set<String> permission = new HashSet<String>(Arrays.asList(user.getPerms().split(","))); simpleAuthorizationInfo.addStringPermissions(permission); return simpleAuthorizationInfo; } /** * 默認使用此方法進行用戶正確與否驗證,錯誤拋出異常便可 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String token = (String) authenticationToken.getCredentials(); // 解密得到username,用於和數據庫進行對比 String username = JWTUtil.getUsername(token); if (username == null) { throw new AuthenticationException("token 無效!"); } User user = userService.getUser(username); if (user == null) { throw new AuthenticationException("用戶"+username+"不存在") ; } if (!JWTUtil.verify(token, username, user.getPassword())) { throw new AuthenticationException("帳戶密碼錯誤!"); } return new SimpleAuthenticationInfo(token, token, "my_realm"); } } 

ShiroConfig.javaspring

/** * @Title: ShiroConfig.java * @Package www.codepeople.cn.shiro * @Description: * Copyright: Copyright (c) 2019 www.codepeople.cn Inc. All rights reserved. * Website: www.codepeople.cn * 注意:本內容僅限於海南科瀾技術信息有限公司內部傳閱,禁止外泄以及用於其餘的商業目 * @Author 劉仁 * @DateTime 2019年4月1日 下午4:14:32 * @version V1.0 */ package www.codepeople.cn.shiro; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import javax.servlet.Filter; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; /** * @ClassName: ShiroConfig * @Description: * @Author 劉仁 * @DateTime 2019年4月1日 下午4:14:32 */ @Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 添加本身的過濾器而且取名爲jwt Map<String, Filter> filterMap = new HashMap<>(); filterMap.put("jwt", new JWTFilter()); shiroFilterFactoryBean.setFilters(filterMap); shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setUnauthorizedUrl("/401"); /** * 自定義url規則 * http://shiro.apache.org/web.html#urls- */ Map<String, String> filterRuleMap = new LinkedHashMap<String, String>(); // 全部的請求經過咱們本身的JWT filter filterRuleMap.put("/**", "jwt"); // 訪問401和404頁面不經過咱們的Filter filterRuleMap.put("/401", "anon"); filterRuleMap.put("/404", "anon"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterRuleMap); return shiroFilterFactoryBean; } @Bean(name = "securityManager") public DefaultSecurityManager getDefaultSecurityManager(@Qualifier("myRelam") MyRealm myRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myRealm); return securityManager; } @Bean(name="myRelam") public MyRealm getMyRealm() { return new MyRealm(); } /** * 下面的代碼是添加註解支持 */ @Bean @DependsOn("lifecycleBeanPostProcessor") public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); // 強制使用cglib,防止重複代理和可能引發代理出錯的問題 // https://zhuanlan.zhihu.com/p/29161098 defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } } 

JWTUtil.javasql

/** * @Title: JWTUtil.java * @Package www.codepeople.cn.util * @Description: * Copyright: Copyright (c) 2019 www.codepeople.cn Inc. All rights reserved. * Website: www.codepeople.cn * 注意:本內容僅限於海南科瀾技術信息有限公司內部傳閱,禁止外泄以及用於其餘的商業目 * @Author 劉仁 * @DateTime 2019年4月1日 下午4:32:56 * @version V1.0 */ package www.codepeople.cn.util; import java.io.UnsupportedEncodingException; import java.util.Date; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.interfaces.DecodedJWT; /** * @ClassName: JWTUtil * @Description: * @Author 劉仁 * @DateTime 2019年4月1日 下午4:32:56 */ public class JWTUtil { // 過時時間24小時 private static final long EXPRIE_TIME = 24*60*60*1000; public static boolean verify(String token, String username, String secret) { try { Algorithm algorithm = Algorithm.HMAC512(secret); JWTVerifier verifier = JWT.require(algorithm) .withClaim("username", username) .build(); verifier.verify(token); return true; } catch (Exception e) { return false; } } /** * @Title: getUsername * @Description: 獲取token中的信息無需secret解密也能得到 * @Author 劉仁 * @DateTime 2019年4月1日 下午4:42:39 * @param token * @return */ public static String getUsername(String token) { try { DecodedJWT jwt = JWT.decode(token); return jwt.getClaim("username").toString(); } catch (JWTDecodeException e) { return null; } } public static String sign(String username, String secret) throws UnsupportedEncodingException { Date date = new Date(System.currentTimeMillis()+EXPRIE_TIME); Algorithm algorithm = Algorithm.HMAC512(secret); // 附帶username信息 return JWT.create() .withClaim("username", username) .withExpiresAt(date) .sign(algorithm); } } 

登陸功能實現shell

UserController.java數據庫

/** * @Title: UserController.java * @Package www.codepeople.cn.controller * @Description: * Copyright: Copyright (c) 2019 www.codepeople.cn Inc. All rights reserved. * Website: www.codepeople.cn * 注意:本內容僅限於海南科瀾技術信息有限公司內部傳閱,禁止外泄以及用於其餘的商業目 * @Author 劉仁 * @DateTime 2019年4月1日 下午5:29:03 * @version V1.0 */ package www.codepeople.cn.controller; import java.io.UnsupportedEncodingException; import java.util.List; import javax.servlet.http.HttpServletResponse; import org.apache.shiro.authz.UnauthorizedException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import www.codepeople.cn.entity.ResponseBean; import www.codepeople.cn.entity.User; import www.codepeople.cn.service.UserService; import www.codepeople.cn.util.JWTUtil; /** * @ClassName: UserController * @Description: * @Author 劉仁 * @DateTime 2019年4月1日 下午5:29:03 */ @RestController public class UserController { @Autowired private UserService userService; @PostMapping("/login") @ApiOperation(value="登陸功能", notes="用戶密碼登陸") @ApiImplicitParams({ @ApiImplicitParam(name = "username", value = "用戶名", required = true, dataType = "String"), @ApiImplicitParam(name = "password", value = "密碼", required = true, dataType = "String") }) public ResponseBean login(@RequestParam("username") String username, @RequestParam("password") String password, HttpServletResponse response) throws UnsupportedEncodingException { User user = userService.getUser(username); if (user.getPassword().equals(password)) { String token = JWTUtil.sign(username, password); response.setHeader("token", token); return new ResponseBean(200, "登陸成功", token); } else { throw new UnauthorizedException(); } } @GetMapping("/listUser") @ApiOperation(value="獲取用戶列表", notes="獲取用戶列表") public ResponseBean listUser() throws UnsupportedEncodingException { List<User> listUser = userService.listUser(); return new ResponseBean(200, "用戶列表", listUser); } @RequestMapping("/401") @ResponseStatus(HttpStatus.UNAUTHORIZED) public ResponseBean unauthorized() { return new ResponseBean(401, "未受權", null); } } 

爲了方便調試加入了Swagger的架包apache

總體的代碼下載地址:https://gitee.com/VCS/springboot-shiro-jwt-demo

==================================================================

博客地址https://www.codepeople.cn

==================================================================

相關文章
相關標籤/搜索