springboot+springsecurity+mybatis+JWT+Redis?實(shí)現(xiàn)前后端離實(shí)戰(zhàn)教程
寫(xiě)在開(kāi)頭:這篇是實(shí)戰(zhàn)篇,即默認(rèn)各位看官具備相應(yīng)的基礎(chǔ)
一、springboot
1.新建項(xiàng)目
我是用idea,jdk選擇1.8以上
各個(gè)名字自行命名
添加部分依賴(lài),后面再往pom.xml加入(這里忘記改了,springboot的版本我使用的是1.5.3release版本?。?/p>
簡(jiǎn)單的項(xiàng)目搭建好了,下一步
2.application.yml的配置
數(shù)據(jù)源
spring: datasource: username: root password: 123456 url: jdbc:mysql://localhost/springboot?characterEncoding=utf-8&useSSl=false driver-class-name: com.mysql.jdbc.Driver
數(shù)據(jù)池用了druid,pom.xml加入依賴(lài)
<!-- druid數(shù)據(jù)庫(kù)連接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.8</version> </dependency>
添加mabatis、druid,完整配置:
spring: datasource: username: root password: 123456 url: jdbc:mysql://localhost/springboot?characterEncoding=utf-8&useSSl=false driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource #監(jiān)控統(tǒng)計(jì)攔截的filters filters: stat,wall,log4j #druid配置 #配置初始化大小/最小/最大 initialSize: 5 minIdle: 5 maxActive: 20 #獲取連接等待超時(shí)時(shí)間 maxWait: 60000 #間隔多久進(jìn)行一次檢測(cè),檢測(cè)需要關(guān)閉的空閑連接 timeBetweenEvictionRunsMillis: 60000 #一個(gè)連接在池中最小生存的時(shí)間 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false #打開(kāi)PSCache,并指定每個(gè)連接上PSCache的大小。oracle設(shè)為true,mysql設(shè)為false。分庫(kù)分表較多推薦設(shè)置為false poolPreparedStatements: false maxPoolPreparedStatementPerConnectionSize: 20 # 通過(guò)connectProperties屬性來(lái)打開(kāi)mergeSql功能;慢SQL記錄 connectionProperties: druid: stat: mergeSql: true slowSqlMillis: 5000 #mybatis是獨(dú)立節(jié)點(diǎn),需要單獨(dú)配置 mybatis: mapper-locations: classpath*:mapper/*.xml type-aliases-package: com.deceen.demo.entity configuration: map-underscore-to-camel-case: true
3.寫(xiě)一個(gè)小demo
controller層
package com.deceen.demo.controller; import com.deceen.demo.entity.DemoEntity; import com.deceen.demo.service.DemoService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * @author: zzx * @date: 2018/10/25 16:48 * @description: demo */ @RestController @RequestMapping("/test") public class DemoController { @Autowired private DemoService orderService; @RequestMapping("/getUser") public List<DemoEntity> getUser(){ List<DemoEntity> result = orderService.getUser(); return result; } }
service層
package com.deceen.demo.service; import com.deceen.demo.entity.DemoEntity; import java.util.List; /** * @author: zzx * @date: 2018/10/25 17:00 * @description: */ public interface DemoService { List<DemoEntity> getUser(); }
impl
package com.deceen.demo.service.impl; import com.deceen.demo.dao.DemoMapper; import com.deceen.demo.entity.DemoEntity; import com.deceen.demo.service.DemoService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; /** * @author: zzx * @date: 2018/10/25 17:03 * @description: */ @Service public class DemoServiceImpl implements DemoService { @Autowired private DemoMapper demoMapper; @Override public List<DemoEntity> getUser() { return demoMapper.getUser(); } }
dao
package com.deceen.demo.dao; import com.deceen.demo.entity.DemoEntity; import org.springframework.stereotype.Component; import java.util.List; /** * @author: zzx * @date: 2018/10/25 17:05 * @description: */ @Component public interface DemoMapper { List<DemoEntity> getUser(); }
entity
package com.deceen.demo.entity; import lombok.Data; /** * @author: zzx * @date: 2018/10/25 17:01 * @description: */ @Data public class DemoEntity { private Integer id; private Integer age; private String name; private Float height; }
這里介紹一下lombok,感覺(jué)蠻好用的一個(gè)插件,DemoEntity這個(gè)類(lèi)中的@Data注解就是其一,可以自動(dòng)生成getset方法(編譯的時(shí)候),后面也會(huì)用到。教程:http://www.dbjr.com.cn/article/214041.htm
mapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.deceen.demo.dao.DemoMapper"> <select id="getUser" resultType="com.deceen.demo.entity.DemoEntity"> SELECT * FROM girl </select> </mapper>
(相關(guān)的數(shù)據(jù)庫(kù)自己隨便建,跟DemoEntity字段對(duì)上就好了。。。)
訪(fǎng)問(wèn)http://localhost:8080/test/getUser
成功
二、druid
上文提到druid,其實(shí)尚未真正可以使用,下面我們繼續(xù)
1.druid參數(shù)配置
package com.deceen.common.config; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.support.http.StatViewServlet; import com.alibaba.druid.support.http.WebStatFilter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import javax.sql.DataSource; /** * druid參數(shù)配置 * @author zzx * @date 2018/10/15 10:09 */ @Configuration @PropertySource(value = "classpath:application.yml") public class DruidConfiguration { /** * @author zzx * @date 2018-10-15 11:28 * @todo 數(shù)據(jù)源配置 */ @Bean(destroyMethod = "close", initMethod = "init") @ConfigurationProperties(prefix = "spring.datasource") public DataSource druidDataSource() { DruidDataSource druidDataSource = new DruidDataSource(); return druidDataSource; } /** * druid * 注冊(cè)一個(gè)StatViewServlet * @return */ @Bean public ServletRegistrationBean druidStatViewServlet(){ //org.springframework.boot.context.embedded.ServletRegistrationBean提供類(lèi)的進(jìn)行注冊(cè). ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*"); //添加初始化參數(shù):initParams //白名單: servletRegistrationBean.addInitParameter("allow","127.0.0.1"); //IP黑名單 (存在共同時(shí),deny優(yōu)先于allow) : 如果滿(mǎn)足deny的話(huà)提示:Sorry, you are not permitted to view this page. //servletRegistrationBean.addInitParameter("deny","192.168.1.73"); //登錄查看信息的賬號(hào)密碼. servletRegistrationBean.addInitParameter("loginUsername","admin"); servletRegistrationBean.addInitParameter("loginPassword","123456"); //是否能夠重置數(shù)據(jù). servletRegistrationBean.addInitParameter("resetEnable","false"); return servletRegistrationBean; } /** * druid過(guò)濾器 * 注冊(cè)一個(gè):filterRegistrationBean * @return */ @Bean public FilterRegistrationBean druidStatFilter(){ FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter()); //添加過(guò)濾規(guī)則. filterRegistrationBean.addUrlPatterns("/*"); //添加不需要忽略的格式信息. filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); return filterRegistrationBean; } }
訪(fǎng)問(wèn)http://localhost:8080/druid/login.html,賬號(hào)密碼為上面代碼設(shè)置的
成功
三、springsecurity
1.引入相關(guān)依賴(lài)
<!-- Spring Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.36</version> </dependency>
2.寫(xiě)了幾個(gè)工具類(lèi)
ResultEnum
package com.deceen.common.Enums; import lombok.Getter; /** * @author: zzx * @date: 2018/10/15 15:16 * @description: 返回的錯(cuò)誤碼枚舉類(lèi) */ @Getter public enum ResultEnum { SUCCESS(101,"成功"), FAILURE(102,"失敗"), USER_NEED_AUTHORITIES(201,"用戶(hù)未登錄"), USER_LOGIN_FAILED(202,"用戶(hù)賬號(hào)或密碼錯(cuò)誤"), USER_LOGIN_SUCCESS(203,"用戶(hù)登錄成功"), USER_NO_ACCESS(204,"用戶(hù)無(wú)權(quán)訪(fǎng)問(wèn)"), USER_LOGOUT_SUCCESS(205,"用戶(hù)登出成功"), TOKEN_IS_BLACKLIST(206,"此token為黑名單"), LOGIN_IS_OVERDUE(207,"登錄已失效"), ; private Integer code; private String message; ResultEnum(Integer code, String message) { this.code = code; this.message = message; } /** * @author: zzx * @date: 2018-10-15 16:26 * @deprecation:通過(guò)code返回枚舉 */ public static ResultEnum parse(int code){ ResultEnum[] values = values(); for (ResultEnum value : values) { if(value.getCode() == code){ return value; } } throw new RuntimeException("Unknown code of ResultEnum"); } }
ResultVO
package com.deceen.common.VO; import com.deceen.common.Enums.ResultEnum; import java.io.Serializable; import java.util.HashMap; import java.util.Map; /** * @author: zzx * @date: 2018/10/15 15:00 * @description: 返回的格式 */ public final class ResultVO implements Serializable { private static final long serialVersionUID = 1725159680599612404L; /** * 返回msg,object,以及token * 返回的code為默認(rèn) * @param message * @param data * @param jwtToken * @return */ public final static Map<String, Object> success(String message, Object data,String jwtToken,Boolean success) { Map<String, Object> map = new HashMap<>(); map.put("jwtToken",jwtToken); map.put("code", ResultEnum.SUCCESS.getCode()); map.put("message", message); map.put("success",success); map.put("data", data); return map; } /** * 返回object,以及token * 返回的msg,code為默認(rèn) * @param data * @param jwtToken * @return */ public final static Map<String, Object> success(Object data,String jwtToken) { Map<String, Object> map = new HashMap<String, Object>(); map.put("jwtToken",jwtToken); map.put("code", ResultEnum.SUCCESS.getCode()); map.put("message", ResultEnum.SUCCESS.getMessage()); map.put("data", data); map.put("success",true); return map; } /** * 返回默認(rèn)的信息 * @return */ public final static Map<String, Object> success() { Map<String, Object> map = new HashMap<String, Object>(); map.put("jwtToken",null); map.put("code", ResultEnum.SUCCESS.getCode()); map.put("message", ResultEnum.SUCCESS.getMessage()); map.put("data", null); map.put("success",true); return map; } public final static Map<String, Object> failure(int code, String message,Object data) { Map<String, Object> map = new HashMap<String, Object>(); map.put("code", code); map.put("message", message); map.put("data", data); map.put("success",false); return map; } public final static Map<String, Object> failure(int code, String message) { Map<String, Object> map = new HashMap<String, Object>(); map.put("code", code); map.put("message", message); map.put("data", null); map.put("success",false); return map; } public final static Map<String, Object> failure(ResultEnum respCode, Object data, Boolean success) { return getStringObjectMap(respCode, data,success); } public final static Map<String, Object> failure(ResultEnum respCode, Boolean success) { return getStringObjectMap(respCode,success); } /* * 成功返回特定的狀態(tài)碼和信息 * */ public final static Map<String, Object> result(ResultEnum respCode, Object data, Boolean success) { return getStringObjectMap(respCode, data,success); } private static Map<String, Object> getStringObjectMap(ResultEnum respCode, Object data, Boolean success) { Map<String, Object> map = new HashMap<String, Object>(); map.put("code", respCode.getCode()); map.put("message", respCode.getMessage()); map.put("data", data); map.put("success",success); return map; } /* * 成功返回特定的狀態(tài)碼和信息 * */ public final static Map<String, Object> result(ResultEnum respCode, Boolean success) { return getStringObjectMap(respCode,success); } private static Map<String, Object> getStringObjectMap(ResultEnum respCode, Boolean success) { Map<String, Object> map = new HashMap<String, Object>(); map.put("code", respCode.getCode()); map.put("message", respCode.getMessage()); map.put("data", null); map.put("success",success); return map; } /* * 成功返回特定的狀態(tài)碼和信息 * */ public final static Map<String, Object> result(ResultEnum respCode, String jwtToken, Boolean success) { Map<String, Object> map = new HashMap<String, Object>(); map.put("jwtToken",jwtToken); map.put("code", respCode.getCode()); map.put("message", respCode.getMessage()); map.put("data", null); map.put("success",success); return map; } }
可以看出來(lái),我們后面是會(huì)集成jwt的token過(guò)來(lái)的,看得懂就行,后面根據(jù)需要改
下面就是springsecurity核心幾個(gè)東西啦,具體我還沒(méi)深入,等后面有時(shí)間再寫(xiě)一篇深入一點(diǎn)的(大體分析一下shiro和springsecurity吧。。。)
3.實(shí)現(xiàn)springsecurity各個(gè)核心接口,處理用戶(hù)各種狀態(tài)
實(shí)現(xiàn)AuthenticationEntryPoint接口,處理用戶(hù)未登錄
package com.deceen.common.security; import com.alibaba.fastjson.JSON; import com.deceen.common.Enums.ResultEnum; import com.deceen.common.VO.ResultVO; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author: zzx * @date: 2018/10/15 15:04 * @description: 用戶(hù)未登錄時(shí)返回給前端的數(shù)據(jù) */ @Component public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { httpServletResponse.getWriter().write(JSON.toJSONString(ResultVO.result(ResultEnum.USER_NEED_AUTHORITIES,false))); } }
實(shí)現(xiàn)AccessDeniedHandler接口,處理無(wú)權(quán)登錄的情況
package com.deceen.common.security; import com.alibaba.fastjson.JSON; import com.deceen.common.Enums.ResultEnum; import com.deceen.common.VO.ResultVO; import com.deceen.rehab.user.entity.SelfUserDetails; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author: zzx * @date: 2018/10/15 16:43 * @description: 無(wú)權(quán)訪(fǎng)問(wèn) */ @Component public class AjaxAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { httpServletResponse.getWriter().write(JSON.toJSONString(ResultVO.result(ResultEnum.USER_NO_ACCESS,false))); } }
實(shí)現(xiàn)AuthenticationFailureHandler接口,處理用戶(hù)登錄失敗
package com.deceen.common.security; import com.alibaba.fastjson.JSON; import com.deceen.common.Enums.ResultEnum; import com.deceen.common.VO.ResultVO; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author: zzx * @date: 2018/10/15 15:31 * @description: 用戶(hù)登錄失敗時(shí)返回給前端的數(shù)據(jù) */ @Component public class AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { httpServletResponse.getWriter().write(JSON.toJSONString(ResultVO.result(ResultEnum.USER_LOGIN_FAILED,false))); } }
實(shí)現(xiàn)AuthenticationSuccessHandler接口,處理登錄成功的情況
package com.deceen.common.security; import com.alibaba.fastjson.JSON; import com.deceen.common.Enums.ResultEnum; import com.deceen.common.VO.ResultVO; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author: zzx * @date: 2018/10/15 16:12 * @description: 用戶(hù)登錄成功時(shí)返回給前端的數(shù)據(jù) */ @Component public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { SelfUserDetails userDetails = (SelfUserDetails) authentication.getPrincipal(); String jwtToken = JwtTokenUtil.generateToken(userDetails.getUsername(), 1500); httpServletResponse.getWriter().write(JSON.toJSONString(ResultVO.result(ResultEnum.USER_LOGIN_SUCCESS,jwtToken,true))); } }
實(shí)現(xiàn)LogoutSuccessHandler接口,處理退出成功
package com.deceen.common.security; import com.alibaba.fastjson.JSON; import com.deceen.common.Enums.ResultEnum; import com.deceen.common.VO.ResultVO; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author: zzx * @date: 2018/10/16 9:59 * @description: 登出成功 */ @Component public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { httpServletResponse.getWriter().write(JSON.toJSONString(ResultVO.result(ResultEnum.USER_LOGOUT_SUCCESS,true))); } }
實(shí)現(xiàn)UserDetails實(shí)現(xiàn)自定義對(duì)象
package com.deceen.demo.entity; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.io.Serializable; import java.util.Collection; import java.util.Set; /** * @author: zzx * @date: 2018/10/15 16:58 * @description: 定義user對(duì)象 */ public class SelfUserDetails implements UserDetails, Serializable { private static final long serialVersionUID = 7171722954972237961L; private Integer id; private String username; private String password; private Set<? extends GrantedAuthority> authorities; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; } public void setAuthorities(Set<? extends GrantedAuthority> authorities) { this.authorities = authorities; } @Override public String getPassword() { // 最重點(diǎn)Ⅰ return this.password; } @Override public String getUsername() { // 最重點(diǎn)Ⅱ return this.username; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } //賬號(hào)是否過(guò)期 @Override public boolean isAccountNonExpired() { return true; } //賬號(hào)是否鎖定 @Override public boolean isAccountNonLocked() { return true; } //賬號(hào)憑證是否未過(guò)期 @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } }
2.權(quán)限訪(fǎng)問(wèn)控制
package com.deceen.common.security; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; import javax.servlet.http.HttpServletRequest; import java.util.HashSet; import java.util.Set; /** * @author: zzx * @date: 2018/10/16 9:54 * @description: 權(quán)限訪(fǎng)問(wèn)控制 */ @Component("rbacauthorityservice") public class RbacAuthorityService { public boolean hasPermission(HttpServletRequest request, Authentication authentication) { Object userInfo = authentication.getPrincipal(); boolean hasPermission = false; if (userInfo instanceof UserDetails) { String username = ((UserDetails) userInfo).getUsername(); //獲取資源 Set<String> urls = new HashSet(); // 這些 url 都是要登錄后才能訪(fǎng)問(wèn),且其他的 url 都不能訪(fǎng)問(wèn)! urls.add("/demo/**");//application.yml里設(shè)置了項(xiàng)目路徑,百度一下我就不貼了 Set set2 = new HashSet(); Set set3 = new HashSet(); AntPathMatcher antPathMatcher = new AntPathMatcher(); for (String url : urls) { if (antPathMatcher.match(url, request.getRequestURI())) { hasPermission = true; break; } } return hasPermission; } else { return false; } } }
這里提示一下啊,url記得改
3.jwt生成token的工具類(lèi)
引入依賴(lài)
<!--JWT--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
工具類(lèi)
package com.deceen.common.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.io.InputStream; import java.security.KeyStore; import java.security.PrivateKey; import java.security.PublicKey; import java.util.Date; import java.util.Map; /** * @author: zzx * @date: 2018/10/16 9:06 * @description: jwt生成token */ public class JwtTokenUtil { // 尋找證書(shū)文件 private static InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("jwt.jks"); // 尋找證書(shū)文件 private static PrivateKey privateKey = null; private static PublicKey publicKey = null; static { // 將證書(shū)文件里邊的私鑰公鑰拿出來(lái) try { KeyStore keyStore = KeyStore.getInstance("JKS"); // java key store 固定常量 keyStore.load(inputStream, "123456".toCharArray()); privateKey = (PrivateKey) keyStore.getKey("jwt", "123456".toCharArray()); // jwt 為 命令生成整數(shù)文件時(shí)的別名 publicKey = keyStore.getCertificate("jwt").getPublicKey(); } catch (Exception e) { e.printStackTrace(); } } /** * 生成token * @param subject (主體信息) * @param expirationSeconds 過(guò)期時(shí)間(秒) * @param claims 自定義身份信息 * @return */ public static String generateToken(String subject, int expirationSeconds, Map<String,Object> claims) { return Jwts.builder() .setClaims(claims) .setSubject(subject) .setExpiration(new Date(System.currentTimeMillis() + expirationSeconds * 1000)) // .signWith(SignatureAlgorithm.HS512, salt) // 不使用公鑰私鑰 .signWith(SignatureAlgorithm.RS256, privateKey) .compact(); } /** * @author: zzx * @date: 2018-10-19 09:10 * @deprecation: 解析token,獲得subject中的信息 */ public static String parseToken(String token, String salt) { String subject = null; try { /*Claims claims = Jwts.parser() // .setSigningKey(salt) // 不使用公鑰私鑰 .setSigningKey(publicKey) .parseClaimsJws(token).getBody();*/ subject = getTokenBody(token).getSubject(); } catch (Exception e) { } return subject; } //獲取token自定義屬性 public static Map<String,Object> getClaims(String token){ Map<String,Object> claims = null; try { claims = getTokenBody(token); }catch (Exception e) { } return claims; } // 是否已過(guò)期 public static boolean isExpiration(String token){ return getTokenBody(token).getExpiration().before(new Date()); } private static Claims getTokenBody(String token){ return Jwts.parser() .setSigningKey(publicKey) .parseClaimsJws(token) .getBody(); } }
在這里展開(kāi)一下,為了實(shí)現(xiàn)前后端交互,采用了jjwt的方案,后面會(huì)加入更多的token驗(yàn)證,現(xiàn)在先把基本的東西弄出來(lái)。
大體是思路就是,每次登陸成功會(huì)返回token給前端做本地保存,以后每一次前端請(qǐng)求api都會(huì)在請(qǐng)求頭中帶上這個(gè)token,我們后面加入一個(gè)過(guò)濾器,專(zhuān)門(mén)攔截token然后驗(yàn)證。肯定會(huì)有人說(shuō)token暴露的問(wèn)題,我的解決方案很簡(jiǎn)單,實(shí)現(xiàn)一個(gè)黑名單,每一次登出或失效的token都加入黑名單(這一塊我用redis實(shí)現(xiàn),用其他緩存數(shù)據(jù)庫(kù)都行,就是一個(gè)思路的問(wèn)題)。token生成的時(shí)候也會(huì)在redis加入相應(yīng)刷新時(shí)間和失效時(shí)間(例如:7天免登陸,即在7天內(nèi)會(huì)自動(dòng)刷新用戶(hù)的token;而失效時(shí)間定為十五分鐘,即每個(gè)token只有15分鐘有效時(shí)間,過(guò)了這個(gè)時(shí)間,會(huì)去判斷是否在刷新時(shí)間內(nèi),如果是,則refresh token,并set進(jìn)request的請(qǐng)求頭之中)
參考鏈接:
https://www.cnblogs.com/stulzq/p/9678501.html#commentform(記得看評(píng)論哦,評(píng)論里也有很多精華)
https://segmentfault.com/a/1190000013151506(語(yǔ)言不同,但是思想想通)
jwt攔截器
package com.deceen.common.filters; import com.deceen.common.utils.JwtTokenUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author: zzx * @date: 2018/10/15 17:30 * @description: 確保在一次請(qǐng)求只通過(guò)一次filter,而不需要重復(fù)執(zhí)行 */ @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired SelfUserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String authToken = authHeader.substring("Bearer ".length()); String username = JwtTokenUtil.parseToken(authToken, "_secret"); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (userDetails != null) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } } filterChain.doFilter(request, response); } }
文中的SelfUserDetailsService接下來(lái)繼續(xù)
文中的jwt.jks是jwt證書(shū),你可以自己生成,也可以用我的項(xiàng)目中的,我放到github里,在resource里找
4.springsecurity核心處理
繼承UserDetailsService,用戶(hù)認(rèn)證的業(yè)務(wù)代碼
package com.deceen.demo.service; import com.deceen.demo.entity.SelfUserDetails; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; import java.util.HashSet; import java.util.Set; /** * @author: zzx * @date: 2018/10/15 16:54 * @description: 用戶(hù)認(rèn)證、權(quán)限 */ @Component public class SelfUserDetailsService implements UserDetailsService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //通過(guò)username查詢(xún)用戶(hù) SelfUserDetails user = userMapper.getUser(username); if(user == null){ //仍需要細(xì)化處理 throw new UsernameNotFoundException("該用戶(hù)不存在"); } Set authoritiesSet = new HashSet(); // 模擬從數(shù)據(jù)庫(kù)中獲取用戶(hù)角色 GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN"); authoritiesSet.add(authority); user.setAuthorities(authoritiesSet); log.info("用戶(hù){}驗(yàn)證通過(guò)",username); return user; } }
相應(yīng)dao層
package com.deceen.demo.dao; import com.deceen.demo.entity.SelfUserDetails; import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Component; /** * @author: zzx * @date: 2018/10/18 14:59 * @description: 用戶(hù)dao層 */ @Component public interface UserMapper { //通過(guò)username查詢(xún)用戶(hù) SelfUserDetails getUser(@Param("username") String username); }
相應(yīng)mapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.deceen.demo.dao.UserMapper"> <select id="getUser" parameterType="String" resultType="com.deceen.demo.entity.SelfUserDetails"> SELECT * FROM user where username = #{username} </select> </mapper>
核心處理類(lèi)
package com.deceen.common.config; import com.deceen.common.filters.JwtAuthenticationTokenFilter; import com.deceen.common.security.*; import com.deceen.demo.service.SelfUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; /** * @author: zzx * @date: 2018/10/15 16:47 * @description: */ @Configuration public class SpringSecurityConf extends WebSecurityConfigurerAdapter { @Autowired AjaxAuthenticationEntryPoint authenticationEntryPoint;//未登陸時(shí)返回 JSON 格式的數(shù)據(jù)給前端(否則為 html) @Autowired AjaxAuthenticationSuccessHandler authenticationSuccessHandler; //登錄成功返回的 JSON 格式數(shù)據(jù)給前端(否則為 html) @Autowired AjaxAuthenticationFailureHandler authenticationFailureHandler; //登錄失敗返回的 JSON 格式數(shù)據(jù)給前端(否則為 html) @Autowired AjaxLogoutSuccessHandler logoutSuccessHandler;//注銷(xiāo)成功返回的 JSON 格式數(shù)據(jù)給前端(否則為 登錄時(shí)的 html) @Autowired AjaxAccessDeniedHandler accessDeniedHandler;//無(wú)權(quán)訪(fǎng)問(wèn)返回的 JSON 格式數(shù)據(jù)給前端(否則為 403 html 頁(yè)面) @Autowired SelfUserDetailsService userDetailsService; // 自定義user @Autowired JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // JWT 攔截器 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 加入自定義的安全認(rèn)證 // auth.authenticationProvider(provider); auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { // 去掉 CSRF http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 使用 JWT,關(guān)閉token .and() .httpBasic().authenticationEntryPoint(authenticationEntryPoint) .and() .authorizeRequests()//定義哪些URL需要被保護(hù)、哪些不需要被保護(hù) .anyRequest()//任何請(qǐng)求,登錄后可以訪(fǎng)問(wèn) .access("@rbacauthorityservice.hasPermission(request,authentication)") // RBAC 動(dòng)態(tài) url 認(rèn)證 .and() .formLogin() //開(kāi)啟登錄, 定義當(dāng)需要用戶(hù)登錄時(shí)候,轉(zhuǎn)到的登錄頁(yè)面 // .loginPage("/test/login.html") // .loginProcessingUrl("/login") .successHandler(authenticationSuccessHandler) // 登錄成功 .failureHandler(authenticationFailureHandler) // 登錄失敗 .permitAll() .and() .logout()//默認(rèn)注銷(xiāo)行為為logout .logoutUrl("/logout") .logoutSuccessHandler(logoutSuccessHandler) .permitAll(); // 記住我 http.rememberMe().rememberMeParameter("remember-me") .userDetailsService(userDetailsService).tokenValiditySeconds(1000); http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // 無(wú)權(quán)訪(fǎng)問(wèn) JSON 格式的數(shù)據(jù) http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // JWT Filter } }
有個(gè)點(diǎn)說(shuō)一下:我密碼加密選擇了BCryptPasswordEncoder格式,官方也推薦這個(gè),然后你們可以自行建一個(gè)測(cè)試用戶(hù),記得密碼先用BCryptPasswordEncoder加密一下哦(寫(xiě)一個(gè)createUser方法,密碼加密保存)
測(cè)試
postman模擬請(qǐng)求:
登錄成功:
復(fù)制token,如下操作,進(jìn)行接口測(cè)試:
請(qǐng)求接口成功:
下一篇講解集成redis+token刷新+logback,有興趣就接著看吧
本篇文章的代碼:https://github.com/zzxzzxhao/springboot-springsecurity
springboot+springsecurity+mybatis+JWT+Redis 實(shí)現(xiàn)前后端離(實(shí)戰(zhàn)篇續(xù))
后記:
1.加入簡(jiǎn)單注冊(cè)功能,SpringSecurityConf加入配置放開(kāi)url,DemoController加入如下代碼
/** * 簡(jiǎn)單注冊(cè)功能 * @param username * @param password * @return */ @PostMapping("/register") public Map<String, Object> register(String username,String password){ orderService.register(username,password); return ResultVO.result(ResultEnum.SUCCESS,true); }
(詳細(xì)代碼我上傳到github了)
2.關(guān)于mysql新版本,導(dǎo)致mysql-connector-java版本對(duì)應(yīng)不上的問(wèn)題
mysql版本:
pom.xml更新對(duì)應(yīng)mysql-connector-java版本
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency>
同時(shí)配置文件中關(guān)于數(shù)據(jù)庫(kù)連接地址需要加上&serverTimezone=GMT%2B8,即:
spring: datasource: username: root password: 123 url: jdbc:mysql://localhost/springboot?characterEncoding=utf-8&useSSl=false&serverTimezone=GMT%2B8 driver-class-name: com.mysql.jdbc.Driver
3.關(guān)于自定義登錄的
其實(shí)也很簡(jiǎn)單,但是我就不上傳了,簡(jiǎn)單說(shuō)一下spring security流程
這個(gè)流程很清楚啦
UsernamePasswordAuthenticationToken封裝加密后的密碼以及用戶(hù)信息
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, bCryptPasswordEncoder.encode(password)); try { Authentication authentication = authenticationManager.authenticate(authenticationToken); SecurityContextHolder.getContext().setAuthentication(authentication); //具體實(shí)現(xiàn)。。。 } catch (AuthenticationException e) { //自行處理 }
到此這篇關(guān)于springboot+springsecurity+mybatis+JWT+Redis 實(shí)現(xiàn)前后端離實(shí)戰(zhàn)教程的文章就介紹到這了,更多相關(guān)springboot+springsecurity+mybatis+JWT+Redis 前后端離內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot整合SpringSecurity和JWT的示例
- SpringBoot+Spring Security+JWT實(shí)現(xiàn)RESTful Api權(quán)限控制的方法
- SpringBoot集成Spring Security用JWT令牌實(shí)現(xiàn)登錄和鑒權(quán)的方法
- Springboot集成Spring Security實(shí)現(xiàn)JWT認(rèn)證的步驟詳解
- SpringBoot3.0+SpringSecurity6.0+JWT的實(shí)現(xiàn)
- 詳解SpringBoot+SpringSecurity+jwt整合及初體驗(yàn)
- SpringBoot+SpringSecurity+JWT實(shí)現(xiàn)系統(tǒng)認(rèn)證與授權(quán)示例
- SpringBoot集成Spring security JWT實(shí)現(xiàn)接口權(quán)限認(rèn)證
- SpringBoot3.x接入Security6.x實(shí)現(xiàn)JWT認(rèn)證的完整步驟
- SpringBoot3集成SpringSecurity+JWT的實(shí)現(xiàn)
相關(guān)文章
Spring?Boot使用HMAC-SHA256對(duì)訪(fǎng)問(wèn)密鑰加解密
本文主要介紹了使用HMAC-SHA256算法進(jìn)行客戶(hù)端和服務(wù)端之間的簽名驗(yàn)簽,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12logback ThresholdFilter臨界值日志過(guò)濾器源碼解讀
這篇文章主要為大家介紹了logback ThresholdFilter臨界值日志過(guò)濾器源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11Springboot項(xiàng)目參數(shù)校驗(yàn)方式(Validator)
本文介紹了如何在Spring Boot項(xiàng)目中使用`spring-boot-starter-validation`包和注解來(lái)實(shí)現(xiàn)請(qǐng)求參數(shù)校驗(yàn),主要介紹了校驗(yàn)注解的使用方法、校驗(yàn)失敗的異常捕獲以及`@Validated`的分組功能2025-02-02java?String拼接json的方式實(shí)現(xiàn)
本文主要介紹了java?String拼接json的方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-09-09linux系統(tǒng)下java項(xiàng)目在后臺(tái)啟動(dòng)的4種方式總結(jié)
Linux是集多種功能于一身的操作系統(tǒng),它可以讓用戶(hù)查看和管理當(dāng)下正在運(yùn)行的進(jìn)程,包括Java程序,這篇文章主要給大家總結(jié)介紹了關(guān)于linux系統(tǒng)下java項(xiàng)目在后臺(tái)啟動(dòng)的4種方式,需要的朋友可以參考下2023-10-10如何實(shí)現(xiàn)廣告彈窗觸達(dá)頻率的控制?
這篇文章主要介紹了如何實(shí)現(xiàn)廣告彈窗觸達(dá)頻率的控制,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04IDEA2020.2創(chuàng)建springboot項(xiàng)目卡死在reading maven project的問(wèn)題
這篇文章主要介紹了關(guān)于2020.2IDEA用spring Initializr創(chuàng)建maven的springboot項(xiàng)目卡死在reading maven project的問(wèn)題描述及解決方法,感興趣的朋友跟隨小編一起看看吧2020-09-09