springboot+springsecurity+mybatis+JWT+Redis?實現(xiàn)前后端離實戰(zhàn)教程
寫在開頭:這篇是實戰(zhàn)篇,即默認各位看官具備相應的基礎
一、springboot
1.新建項目
我是用idea,jdk選擇1.8以上

各個名字自行命名

添加部分依賴,后面再往pom.xml加入(這里忘記改了,springboot的版本我使用的是1.5.3release版本?。?/p>

簡單的項目搭建好了,下一步
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加入依賴
<!-- druid數(shù)據(jù)庫連接池 --> <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)計攔截的filters
filters: stat,wall,log4j
#druid配置
#配置初始化大小/最小/最大
initialSize: 5
minIdle: 5
maxActive: 20
#獲取連接等待超時時間
maxWait: 60000
#間隔多久進行一次檢測,檢測需要關閉的空閑連接
timeBetweenEvictionRunsMillis: 60000
#一個連接在池中最小生存的時間
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
#打開PSCache,并指定每個連接上PSCache的大小。oracle設為true,mysql設為false。分庫分表較多推薦設置為false
poolPreparedStatements: false
maxPoolPreparedStatementPerConnectionSize: 20
# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
connectionProperties:
druid:
stat:
mergeSql: true
slowSqlMillis: 5000
#mybatis是獨立節(jié)點,需要單獨配置
mybatis:
mapper-locations: classpath*:mapper/*.xml
type-aliases-package: com.deceen.demo.entity
configuration:
map-underscore-to-camel-case: true3.寫一個小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,感覺蠻好用的一個插件,DemoEntity這個類中的@Data注解就是其一,可以自動生成getset方法(編譯的時候),后面也會用到。教程: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>(相關的數(shù)據(jù)庫自己隨便建,跟DemoEntity字段對上就好了。。。)
訪問http://localhost:8080/test/getUser

成功
二、druid
上文提到druid,其實尚未真正可以使用,下面我們繼續(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
* 注冊一個StatViewServlet
* @return
*/
@Bean
public ServletRegistrationBean druidStatViewServlet(){
//org.springframework.boot.context.embedded.ServletRegistrationBean提供類的進行注冊.
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
//添加初始化參數(shù):initParams
//白名單:
servletRegistrationBean.addInitParameter("allow","127.0.0.1");
//IP黑名單 (存在共同時,deny優(yōu)先于allow) : 如果滿足deny的話提示:Sorry, you are not permitted to view this page.
//servletRegistrationBean.addInitParameter("deny","192.168.1.73");
//登錄查看信息的賬號密碼.
servletRegistrationBean.addInitParameter("loginUsername","admin");
servletRegistrationBean.addInitParameter("loginPassword","123456");
//是否能夠重置數(shù)據(jù).
servletRegistrationBean.addInitParameter("resetEnable","false");
return servletRegistrationBean;
}
/**
* druid過濾器
* 注冊一個:filterRegistrationBean
* @return
*/
@Bean
public FilterRegistrationBean druidStatFilter(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
//添加過濾規(guī)則.
filterRegistrationBean.addUrlPatterns("/*");
//添加不需要忽略的格式信息.
filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}訪問http://localhost:8080/druid/login.html,賬號密碼為上面代碼設置的

成功
三、springsecurity
1.引入相關依賴
<!-- 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.寫了幾個工具類
ResultEnum
package com.deceen.common.Enums;
import lombok.Getter;
/**
* @author: zzx
* @date: 2018/10/15 15:16
* @description: 返回的錯誤碼枚舉類
*/
@Getter
public enum ResultEnum {
SUCCESS(101,"成功"),
FAILURE(102,"失敗"),
USER_NEED_AUTHORITIES(201,"用戶未登錄"),
USER_LOGIN_FAILED(202,"用戶賬號或密碼錯誤"),
USER_LOGIN_SUCCESS(203,"用戶登錄成功"),
USER_NO_ACCESS(204,"用戶無權訪問"),
USER_LOGOUT_SUCCESS(205,"用戶登出成功"),
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:通過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為默認
* @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為默認
* @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;
}
/**
* 返回默認的信息
* @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;
}
}可以看出來,我們后面是會集成jwt的token過來的,看得懂就行,后面根據(jù)需要改
下面就是springsecurity核心幾個東西啦,具體我還沒深入,等后面有時間再寫一篇深入一點的(大體分析一下shiro和springsecurity吧。。。)
3.實現(xiàn)springsecurity各個核心接口,處理用戶各種狀態(tài)
實現(xiàn)AuthenticationEntryPoint接口,處理用戶未登錄
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: 用戶未登錄時返回給前端的數(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)));
}
}實現(xiàn)AccessDeniedHandler接口,處理無權登錄的情況
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: 無權訪問
*/
@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)));
}
}實現(xiàn)AuthenticationFailureHandler接口,處理用戶登錄失敗
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: 用戶登錄失敗時返回給前端的數(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)));
}
}實現(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: 用戶登錄成功時返回給前端的數(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)));
}
}實現(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)));
}
}實現(xiàn)UserDetails實現(xiàn)自定義對象
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對象
*/
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() { // 最重點Ⅰ
return this.password;
}
@Override
public String getUsername() { // 最重點Ⅱ
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
//賬號是否過期
@Override
public boolean isAccountNonExpired() {
return true;
}
//賬號是否鎖定
@Override
public boolean isAccountNonLocked() {
return true;
}
//賬號憑證是否未過期
@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.權限訪問控制
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: 權限訪問控制
*/
@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 都是要登錄后才能訪問,且其他的 url 都不能訪問!
urls.add("/demo/**");//application.yml里設置了項目路徑,百度一下我就不貼了
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的工具類
引入依賴
<!--JWT--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
工具類
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 {
// 尋找證書文件
private static InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("jwt.jks"); // 尋找證書文件
private static PrivateKey privateKey = null;
private static PublicKey publicKey = null;
static { // 將證書文件里邊的私鑰公鑰拿出來
try {
KeyStore keyStore = KeyStore.getInstance("JKS"); // java key store 固定常量
keyStore.load(inputStream, "123456".toCharArray());
privateKey = (PrivateKey) keyStore.getKey("jwt", "123456".toCharArray()); // jwt 為 命令生成整數(shù)文件時的別名
publicKey = keyStore.getCertificate("jwt").getPublicKey();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 生成token
* @param subject (主體信息)
* @param expirationSeconds 過期時間(秒)
* @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;
}
// 是否已過期
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();
}
}在這里展開一下,為了實現(xiàn)前后端交互,采用了jjwt的方案,后面會加入更多的token驗證,現(xiàn)在先把基本的東西弄出來。
大體是思路就是,每次登陸成功會返回token給前端做本地保存,以后每一次前端請求api都會在請求頭中帶上這個token,我們后面加入一個過濾器,專門攔截token然后驗證??隙〞腥苏ftoken暴露的問題,我的解決方案很簡單,實現(xiàn)一個黑名單,每一次登出或失效的token都加入黑名單(這一塊我用redis實現(xiàn),用其他緩存數(shù)據(jù)庫都行,就是一個思路的問題)。token生成的時候也會在redis加入相應刷新時間和失效時間(例如:7天免登陸,即在7天內會自動刷新用戶的token;而失效時間定為十五分鐘,即每個token只有15分鐘有效時間,過了這個時間,會去判斷是否在刷新時間內,如果是,則refresh token,并set進request的請求頭之中)
參考鏈接:
https://www.cnblogs.com/stulzq/p/9678501.html#commentform(記得看評論哦,評論里也有很多精華)
https://segmentfault.com/a/1190000013151506(語言不同,但是思想想通)
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: 確保在一次請求只通過一次filter,而不需要重復執(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接下來繼續(xù)
文中的jwt.jks是jwt證書,你可以自己生成,也可以用我的項目中的,我放到github里,在resource里找
4.springsecurity核心處理
繼承UserDetailsService,用戶認證的業(yè)務代碼
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: 用戶認證、權限
*/
@Component
public class SelfUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//通過username查詢用戶
SelfUserDetails user = userMapper.getUser(username);
if(user == null){
//仍需要細化處理
throw new UsernameNotFoundException("該用戶不存在");
}
Set authoritiesSet = new HashSet();
// 模擬從數(shù)據(jù)庫中獲取用戶角色
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN");
authoritiesSet.add(authority);
user.setAuthorities(authoritiesSet);
log.info("用戶{}驗證通過",username);
return user;
}
}相應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: 用戶dao層
*/
@Component
public interface UserMapper {
//通過username查詢用戶
SelfUserDetails getUser(@Param("username") String username);
}相應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>核心處理類
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;//未登陸時返回 JSON 格式的數(shù)據(jù)給前端(否則為 html)
@Autowired
AjaxAuthenticationSuccessHandler authenticationSuccessHandler; //登錄成功返回的 JSON 格式數(shù)據(jù)給前端(否則為 html)
@Autowired
AjaxAuthenticationFailureHandler authenticationFailureHandler; //登錄失敗返回的 JSON 格式數(shù)據(jù)給前端(否則為 html)
@Autowired
AjaxLogoutSuccessHandler logoutSuccessHandler;//注銷成功返回的 JSON 格式數(shù)據(jù)給前端(否則為 登錄時的 html)
@Autowired
AjaxAccessDeniedHandler accessDeniedHandler;//無權訪問返回的 JSON 格式數(shù)據(jù)給前端(否則為 403 html 頁面)
@Autowired
SelfUserDetailsService userDetailsService; // 自定義user
@Autowired
JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // JWT 攔截器
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 加入自定義的安全認證
// 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,關閉token
.and()
.httpBasic().authenticationEntryPoint(authenticationEntryPoint)
.and()
.authorizeRequests()//定義哪些URL需要被保護、哪些不需要被保護
.anyRequest()//任何請求,登錄后可以訪問
.access("@rbacauthorityservice.hasPermission(request,authentication)") // RBAC 動態(tài) url 認證
.and()
.formLogin() //開啟登錄, 定義當需要用戶登錄時候,轉到的登錄頁面
// .loginPage("/test/login.html")
// .loginProcessingUrl("/login")
.successHandler(authenticationSuccessHandler) // 登錄成功
.failureHandler(authenticationFailureHandler) // 登錄失敗
.permitAll()
.and()
.logout()//默認注銷行為為logout
.logoutUrl("/logout")
.logoutSuccessHandler(logoutSuccessHandler)
.permitAll();
// 記住我
http.rememberMe().rememberMeParameter("remember-me")
.userDetailsService(userDetailsService).tokenValiditySeconds(1000);
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // 無權訪問 JSON 格式的數(shù)據(jù)
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // JWT Filter
}
}有個點說一下:我密碼加密選擇了BCryptPasswordEncoder格式,官方也推薦這個,然后你們可以自行建一個測試用戶,記得密碼先用BCryptPasswordEncoder加密一下哦(寫一個createUser方法,密碼加密保存)
測試
postman模擬請求:
登錄成功:

復制token,如下操作,進行接口測試:

請求接口成功:

下一篇講解集成redis+token刷新+logback,有興趣就接著看吧
本篇文章的代碼:https://github.com/zzxzzxhao/springboot-springsecurity
springboot+springsecurity+mybatis+JWT+Redis 實現(xiàn)前后端離(實戰(zhàn)篇續(xù))
后記:
1.加入簡單注冊功能,SpringSecurityConf加入配置放開url,DemoController加入如下代碼
/**
* 簡單注冊功能
* @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);
}(詳細代碼我上傳到github了)
2.關于mysql新版本,導致mysql-connector-java版本對應不上的問題
mysql版本:

pom.xml更新對應mysql-connector-java版本
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency>
同時配置文件中關于數(shù)據(jù)庫連接地址需要加上&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.Driver3.關于自定義登錄的
其實也很簡單,但是我就不上傳了,簡單說一下spring security流程

這個流程很清楚啦
UsernamePasswordAuthenticationToken封裝加密后的密碼以及用戶信息
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, bCryptPasswordEncoder.encode(password));
try {
Authentication authentication = authenticationManager.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
//具體實現(xiàn)。。。
} catch (AuthenticationException e) {
//自行處理
}到此這篇關于springboot+springsecurity+mybatis+JWT+Redis 實現(xiàn)前后端離實戰(zhàn)教程的文章就介紹到這了,更多相關springboot+springsecurity+mybatis+JWT+Redis 前后端離內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- 如何使用jwt+redis實現(xiàn)單點登錄
- 使用Redis實現(xiàn)JWT令牌主動失效機制
- SpringSecurity+Redis+Jwt實現(xiàn)用戶認證授權
- Shiro整合Springboot和redis,jwt過程中的錯誤shiroFilterChainDefinition問題
- jwt+redis實現(xiàn)登錄認證的示例代碼
- 基于 Redis 的 JWT令牌失效處理方案(實現(xiàn)步驟)
- SpringBoot整合SpringSecurity和JWT和Redis實現(xiàn)統(tǒng)一鑒權認證
- SpringSecurity+jwt+redis基于數(shù)據(jù)庫登錄認證的實現(xiàn)
- java實現(xiàn)認證與授權的jwt與token+redis,哪種方案更好用?
相關文章
企業(yè)級Kubernetes管理平臺Wayne功能特性介紹
這篇文章主要為大家介紹了企業(yè)級Kubernetes管理平臺Wayne的功能特性及架構設計,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步2022-02-02
springboot實現(xiàn)學生管理系統(tǒng)
這篇文章主要為大家詳細介紹了springboot實現(xiàn)學生管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-07-07
SpringBoot多環(huán)境切換的靈活配置詳細教程
在真實項目開發(fā)的時候,一定會有多個環(huán)境,下面這篇文章主要給大家介紹了關于SpringBoot多環(huán)境切換靈活配置的相關資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2024-04-04
Java使用ant.jar執(zhí)行SQL腳本文件的示例代碼
這篇文章主要介紹了Java使用ant.jar執(zhí)行SQL腳本文件,文中通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2024-02-02
Java中StringBuilder與StringBuffer使用及源碼解讀
我們前面學習的String就屬于不可變字符串,因為理論上一個String字符串一旦定義好,其內容就不可再被改變,但實際上,還有另一種可變字符串,包括StringBuilder和StringBuffer兩個類,那可變字符串有什么特點,又怎么使用呢,接下來就請大家跟我一起來學習吧2023-05-05

