使用spring boot+shiro+jwt+mybatis-plus搭建项目框架
生活随笔
收集整理的這篇文章主要介紹了
使用spring boot+shiro+jwt+mybatis-plus搭建项目框架
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
1.創建spring boot項目,并導入依賴
pom.xml
| <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>com.microsoft.sqlserver</groupId><artifactId>mssql-jdbc</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.0.1</version></dependency><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.2.13</version></dependency><dependency><groupId>org.apache.velocity</groupId><artifactId>velocity</artifactId><version>1.7</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.69</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.3.2</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.3</version></dependency><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>3.14.0</version></dependency><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.8.3</version></dependency><!-- https://mvnrepository.com/artifact/com.alibaba/druid --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.21</version></dependency><!-- 緩存 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.json</groupId><artifactId>json</artifactId><version>20180130</version></dependency></dependencies> |
2.在配置文件中配置掃描mybatis的mapper.xml的路徑
?
| mybatis-plus.mapper-locations=classpath*:mapper/*.xml |
3.配置shiro從數據庫動態讀取的realm
?
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.shiro.mapper.RfgcglMapper; import com.shiro.pojo.RFMenu; import com.shiro.pojo.Rfgcgl; import com.shiro.pojo.RfrelRoleMenu; import com.shiro.pojo.Rfrole; import lombok.extern.slf4j.Slf4j; 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 org.springframework.beans.factory.annotation.Value;import java.util.HashSet; import java.util.List; import java.util.Set;/*** MyRealm:自定義一個授權** @author zhangxiaoxiang* @date: 2019/07/12*/@Slf4j public class JwtRealm extends AuthorizingRealm {@Autowiredprivate RfgcglMapper rfgcglMapper;@Value("${admin.username}")private String adminname;@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof JwtToken;}/*** 只有當需要檢測用戶權限的時候才會調用此方法,例如checkRole,checkPermission之類的** @param principals* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {//保存所有角色名Set<String> allRoles = new HashSet<>();//保存所有權限名Set<String> allPermissions = new HashSet<>();String username = JwtUtil.getUsername(principals.toString());if (username.equals(adminname)){ // allRoles.add("admin");SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); // simpleAuthorizationInfo.addRoles(allRoles);List<RFMenu> rfMenus = new RFMenu().selectList(new QueryWrapper<RFMenu>().ne("MenuCaption",""));for (RFMenu m: rfMenus) {allPermissions.add(m.getMenuBrief());}simpleAuthorizationInfo.addStringPermissions(allPermissions);return simpleAuthorizationInfo;}Rfgcgl user = rfgcglMapper.selectOne(new QueryWrapper<Rfgcgl>().eq("YHMC", username));//查詢對應角色Rfrole rfrole = new Rfrole().selectById(user.getRoleid());allRoles.add(rfrole.getRolename());//查詢所有權限List<RfrelRoleMenu> rfrelRoleMenus = new RfrelRoleMenu().selectList(new QueryWrapper<Rfrole>().eq("roleid", rfrole.getId()));for (RfrelRoleMenu rm : rfrelRoleMenus) {RFMenu menu = new RFMenu();menu = menu.selectOne(new QueryWrapper<RFMenu>().eq("Lsh", rm.getMenuid()));allPermissions.add(menu.getMenuBrief());}SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();simpleAuthorizationInfo.addRoles(allRoles);simpleAuthorizationInfo.addStringPermissions(allPermissions);return simpleAuthorizationInfo;}//用戶認證@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {String token = (String) auth.getCredentials();// 解密獲得username,用于和數據庫進行對比String username = null;try {//這里工具類沒有處理空指針等異常這里處理一下(這里處理科學一些)username = JwtUtil.getUsername(token);} catch (Exception e) {throw new AuthenticationException("heard的token拼寫錯誤或者值為空");}if (username == null) {log.error("token無效(空''或者null都不行!)");throw new AuthenticationException("token無效");}//如果用戶是超級管理員,直接返回數據if(username.equals(adminname)){return new SimpleAuthenticationInfo(token, token, "my_realm");}Rfgcgl userBean = rfgcglMapper.selectOne(new QueryWrapper<Rfgcgl>().eq("YHMC", username));if (userBean == null) {log.error("用戶不存在!)");throw new AuthenticationException("用戶不存在!");}boolean verify = JwtUtil.verify(token, username, userBean.getYhkl());if (!verify) {log.error("用戶名或密碼錯誤(token無效或者與登錄者不匹配)!)");throw new AuthenticationException("用戶名或密碼錯誤(token無效或者與登錄者不匹配)!"); // throw new AppException(999,"用戶名或密碼錯誤(token無效或者與登錄者不匹配)!");}return new SimpleAuthenticationInfo(token, token, "my_realm");} } |
4.配置攔截請求頭參數的過濾器
| import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMethod;import javax.servlet.Filter; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;/*** JwtFilter:jwt過濾器來作為shiro的過濾器** @author zhangxiaoxiang* @date: 2019/07/12*/ @Slf4j @Component//這個注入與否影響不大 public class JwtFilter extends BasicHttpAuthenticationFilter implements Filter {/*** 執行登錄* @param request* @param response* @return* @throws Exception*/@Overrideprotected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpServletRequest = (HttpServletRequest) request;String token = httpServletRequest.getHeader("Token");JwtToken jwtToken = new JwtToken(token);// 提交給realm進行登入,如果錯誤他會拋出異常并被捕獲try {getSubject(request, response).login(jwtToken);// 如果沒有拋出異常則代表登入成功,返回truereturn true;} catch (AuthenticationException e) { // ResponseData responseData = ResponseDataUtil.authorizationFailed( "沒有訪問權限,原因是:" + e.getMessage());ResultInfo responseData = ResultInfo.error( 401,"請求失敗,原因是:" + e.getMessage());//SerializerFeature.WriteMapNullValue為了null屬性也輸出json的鍵值對Object o = JSONObject.toJSONString(responseData, SerializerFeature.WriteMapNullValue);response.setCharacterEncoding("utf-8");response.getWriter().print(o);return false;}}/*** 執行登錄認證** @param request* @param response* @param mappedValue* @return*/@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {try {return executeLogin(request, response);// return true;有一篇博客這里直接返回true是不正確的,在這里我特別指出一下} catch (Exception e) {log.error("JwtFilter過濾驗證失敗!");return false;}}/*** 對跨域提供支持* @param request* @param response* @return* @throws Exception*/@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);} |
?5.配置shiro
| import com.shiro.config.JwtFilter; import com.shiro.config.JwtRealm; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.mgt.SecurityManager; 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.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn;import javax.servlet.Filter; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map;/*** ShiroConfig:shiro 配置類,配置哪些攔截,哪些不攔截,哪些授權等等各種配置都在這里* @author zhangxiaoxiang* @date: 2019/07/12*/@Configuration @Slf4j public class ShiroConfig {/*** Shiro生命周期處理器*/@Bean(name = "lifecycleBeanPostProcessor")public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}/*** 開啟Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP掃描使用Shiro注解的類,并在必要時進行安全邏輯驗證*/@Bean@DependsOn("lifecycleBeanPostProcessor")public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();creator.setProxyTargetClass(true); // creator.setUsePrefix(true);return creator;}/*** 開啟shiro aop注解支持.* 使用代理方式;所以需要開啟代碼支持;*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){AuthorizationAttributeSourceAdvisor attributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();//設置安全管理器attributeSourceAdvisor.setSecurityManager(securityManager);return attributeSourceAdvisor;}@Beanpublic JwtRealm jwtRealm(){JwtRealm jwtRealm = new JwtRealm();return jwtRealm;}/*** 注入安全管理器* @param* @return*/@Bean(name = "securityManager")public DefaultWebSecurityManager getDefaultWebSecurityManager() {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager.setRealm(jwtRealm()); // 關聯realmreturn securityManager;}/*** 注入安全過濾器* @param securityManager* @return*/@Bean("shiroFilter")public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);//攔截器Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();// 配置不會被攔截的鏈接 順序判斷filterChainDefinitionMap.put("/api/token", "anon"); // filterChainDefinitionMap.put("/websocket/**", "anon");filterChainDefinitionMap.put("/api/login", "anon"); // filterChainDefinitionMap.put("/upload/**","anon");注釋后訪問圖片需要請求頭有token信息filterChainDefinitionMap.put("/druid/**", "anon");filterChainDefinitionMap.put("/swagger-ui.html", "anon");filterChainDefinitionMap.put("/webjars/springfox-swagger-ui/**", "anon");filterChainDefinitionMap.put("/swagger-resources/**", "anon");filterChainDefinitionMap.put("/v2/api-docs", "anon"); // filterChainDefinitionMap.put("/**","authc"); // // 添加自己的過濾器并且取名為jwt // Map<String, Filter> filterMap = new HashMap<String, Filter>(); // filterMap.put("authc", new ShiroUserFilter()); // shiroFilterFactoryBean.setFilters(filterMap); // //<!-- 過濾鏈定義,從上向下順序執行,一般將/**放在最為下邊 // filterChainDefinitionMap.put("/**","authc");// 添加自己的過濾器并且取名為jwtMap<String, Filter> filterMap = new HashMap<String, Filter>(1);filterMap.put("jwt", new JwtFilter());shiroFilterFactoryBean.setFilters(filterMap);//<!-- 過濾鏈定義,從上向下順序執行,一般將/**放在最為下邊filterChainDefinitionMap.put("/**", "jwt");//未授權界面;shiroFilterFactoryBean.setUnauthorizedUrl("/403");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}} |
?6.controller層實現
登錄
| @PostMapping("/api/login") public ResultInfo login(String username, String password, String captcha){Map<String,Object> map = new HashMap<>();try {if(StringUtils.isBlank(username)||StringUtils.isBlank(password)||StringUtils.isBlank(captcha)){return ResultInfo.fail(902,"username或password或captcha不能為空");}String token = JwtUtil.sign(username, password);map.put("token",token);map.put("user",user);return ResultInfo.ok(map);}catch (Exception e) {e.printStackTrace();String ex = e.getClass().getName();if (ex != null) {if (UnknownAccountException.class.getName().equals(ex)) {return ResultInfo.fail(904,"用戶名不存在");} else if (IncorrectCredentialsException.class.getName().equals(ex)) {return ResultInfo.fail(905,"賬戶或密碼錯誤");}else if(AuthenticationException.class.getName().equals(ex)){return ResultInfo.fail(906,"token無效");}else {return ResultInfo.fail(907,"登錄失敗");}}return ResultInfo.fail(901,"登錄失敗");} } |
?普通查詢
| @RequiresPermissions("數據查詢") @ApiOperation("民族列表") @PostMapping("/query/mz") public ResultInfo getMzList(){Map<String,Object> map = new HashMap<>();try {List<String> list = xkSqMapper.getMz();map.put("datas",list);return ResultInfo.ok(map);}catch (Exception e){return ResultInfo.fail(901,"獲取民族列表失敗");} } |
啟動項目就可以訪問controller中的接口了。
項目中用到的工具類
JwtToken| import org.apache.shiro.authc.AuthenticationToken;/*** JwtToken:實現shiro的AuthenticationToken接口的類JwtToken** @author zhangxiaoxiang* @date: 2019/07/12*/ 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;} } |
| 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; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component;import java.util.Date;/*** JwtUtil:用來進行簽名和效驗Token** @author zhangxiaoxiang* @date: 2019/07/12*/ @Slf4j @Component public class JwtUtil {/*** JWT驗證過期時間 EXPIRE_TIME 分鐘*/private static final long EXPIRE_TIME = 30 * 60 * 1000;/*** 校驗token是否正確** @param token 密鑰* @param secret 用戶的密碼* @return 是否正確*/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) {exception.printStackTrace();log.error("JwtUtil登錄驗證失敗!");return false;}}/*** 獲得token中的信息無需secret解密也能獲得** @return token中包含的用戶名*/public static String getUsername(String token) {try {DecodedJWT jwt = JWT.decode(token);return jwt.getClaim("username").asString();} catch (JWTDecodeException e) {return null;}}/*** 生成token簽名EXPIRE_TIME 分鐘后過期** @param username 用戶名(電話號碼)* @param secret 用戶的密碼* @return 加密的token*/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);} } |
| import com.alibaba.fastjson.JSONObject;import java.util.LinkedHashMap; import java.util.List; import java.util.Map;/*** Api響應對象*/public class ResultInfo extends LinkedHashMap<String, Object> {private static final long serialVersionUID = -1052650877920800810L;private ResultInfo() {}public static ResultInfo ok() {return new ResultInfo().code(200);}public static ResultInfo ok(String msg) {return new ResultInfo().code(200).msg(msg);}public static ResultInfo ok(Map<String, Object> data) {return new ResultInfo().code(200).data(data);}public static ResultInfo ok(List<Map<String, Object>> datas) {return new ResultInfo().code(200).datas(datas);}public static ResultInfo ok(Map<String, Object> data, List<Map<String, Object>> datas) {return new ResultInfo().code(200).data(data).datas(datas);}public static ResultInfo ok(String msg, Map<String, Object> data) {return new ResultInfo().code(200).msg(msg).data(data);}public static ResultInfo ok(String msg, List<Map<String, Object>> datas) {return new ResultInfo().code(200).msg(msg).datas(datas);}public static ResultInfo ok(String msg, Map<String, Object> data, List<Map<String, Object>> datas) {return new ResultInfo().code(200).msg(msg).data(data).datas(datas);}public static ResultInfo error(int code) {return new ResultInfo().code(code);}public static ResultInfo error(int code, String msg) {return new ResultInfo().code(code).msg(msg);}public static ResultInfo fail(int err) {return new ResultInfo().code(err);}public static ResultInfo fail(int err, String msg) {return new ResultInfo().code(err).msg(msg);}private ResultInfo code(Integer code) {this.put("code", code);return this;}private ResultInfo msg(String msg) {this.put("message", msg);return this;}private ResultInfo data(Map<String, Object> data) {this.put("data", data);return this;}private ResultInfo datas(List<Map<String, Object>> datas) {this.put("datas", datas);return this;}@Overridepublic String toString() {return JSONObject.toJSONString(this).toString();} } |
?
總結
以上是生活随笔為你收集整理的使用spring boot+shiro+jwt+mybatis-plus搭建项目框架的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: spring boot+mybatis执
- 下一篇: class path resource