簡介 JSON Web Token(縮寫 JWT)是目前最流行的跨域認證解決方案。 "JSON Web Token 入門教程 阮一峰" ,這篇文章可以幫你瞭解JWT的概念。本文重點講解Spring Boot 結合 jwt ,來實現前後端分離中,介面的安全調用。 快速上手 之前的文章已經對 Sprin ...
簡介
JSON Web Token(縮寫 JWT)是目前最流行的跨域認證解決方案。JSON Web Token 入門教程 - 阮一峰,這篇文章可以幫你瞭解JWT的概念。本文重點講解Spring Boot 結合 jwt ,來實現前後端分離中,介面的安全調用。
快速上手
之前的文章已經對 Spring Security 進行了講解,這一節對涉及到 Spring Security 的配置不詳細講解。若不瞭解 Spring Security 先移步到 Spring Boot Security 詳解。
建表
DROP TABLE IF EXISTS `user`;
DROP TABLE IF EXISTS `role`;
DROP TABLE IF EXISTS `user_role`;
DROP TABLE IF EXISTS `role_permission`;
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `user` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `role` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE `user_role` (
`user_id` bigint(11) NOT NULL,
`role_id` bigint(11) NOT NULL
);
CREATE TABLE `role_permission` (
`role_id` bigint(11) NOT NULL,
`permission_id` bigint(11) NOT NULL
);
CREATE TABLE `permission` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`url` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`description` varchar(255) NULL,
`pid` bigint(11) NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO user (id, username, password) VALUES (1,'user','e10adc3949ba59abbe56e057f20f883e');
INSERT INTO user (id, username , password) VALUES (2,'admin','e10adc3949ba59abbe56e057f20f883e');
INSERT INTO role (id, name) VALUES (1,'USER');
INSERT INTO role (id, name) VALUES (2,'ADMIN');
INSERT INTO permission (id, url, name, pid) VALUES (1,'/user/hi','',0);
INSERT INTO permission (id, url, name, pid) VALUES (2,'/admin/hi','',0);
INSERT INTO user_role (user_id, role_id) VALUES (1, 1);
INSERT INTO user_role (user_id, role_id) VALUES (2, 1);
INSERT INTO user_role (user_id, role_id) VALUES (2, 2);
INSERT INTO role_permission (role_id, permission_id) VALUES (1, 1);
INSERT INTO role_permission (role_id, permission_id) VALUES (2, 1);
INSERT INTO role_permission (role_id, permission_id) VALUES (2, 2);
項目結構
resources
|___application.yml
java
|___com
| |____gf
| | |____SpringbootJwtApplication.java
| | |____config
| | | |____.DS_Store
| | | |____SecurityConfig.java
| | | |____MyFilterSecurityInterceptor.java
| | | |____MyInvocationSecurityMetadataSourceService.java
| | | |____MyAccessDecisionManager.java
| | |____entity
| | | |____User.java
| | | |____RolePermisson.java
| | | |____Role.java
| | |____mapper
| | | |____PermissionMapper.java
| | | |____UserMapper.java
| | | |____RoleMapper.java
| | |____utils
| | | |____JwtTokenUtil.java
| | |____controller
| | | |____AuthController.java
| | |____filter
| | | |____JwtTokenFilter.java
| | |____service
| | | |____impl
| | | | |____AuthServiceImpl.java
| | | | |____UserDetailsServiceImpl.java
| | | |____AuthService.java
關鍵代碼
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/spring-security-jwt?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
SecurityConfig
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//校驗用戶
auth.userDetailsService( userDetailsService ).passwordEncoder( new PasswordEncoder() {
//對密碼進行加密
@Override
public String encode(CharSequence charSequence) {
System.out.println(charSequence.toString());
return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
}
//對密碼進行判斷匹配
@Override
public boolean matches(CharSequence charSequence, String s) {
String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
boolean res = s.equals( encode );
return res;
}
} );
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
//因為使用JWT,所以不需要HttpSession
.sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
//OPTIONS請求全部放行
.antMatchers( HttpMethod.OPTIONS, "/**").permitAll()
//登錄介面放行
.antMatchers("/auth/login").permitAll()
//其他介面全部接受驗證
.anyRequest().authenticated();
//使用自定義的 Token過濾器 驗證請求的Token是否合法
http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
http.headers().cacheControl();
}
@Bean
public JwtTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtTokenFilter();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
JwtTokenUtil
/**
* JWT 工具類
*/
@Component
public class JwtTokenUtil implements Serializable {
private static final String CLAIM_KEY_USERNAME = "sub";
/**
* 5天(毫秒)
*/
private static final long EXPIRATION_TIME = 432000000;
/**
* JWT密碼
*/
private static final String SECRET = "secret";
/**
* 簽發JWT
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>(16);
claims.put( CLAIM_KEY_USERNAME, userDetails.getUsername() );
return Jwts.builder()
.setClaims( claims )
.setExpiration( new Date( Instant.now().toEpochMilli() + EXPIRATION_TIME ) )
.signWith( SignatureAlgorithm.HS512, SECRET )
.compact();
}
/**
* 驗證JWT
*/
public Boolean validateToken(String token, UserDetails userDetails) {
User user = (User) userDetails;
String username = getUsernameFromToken( token );
return (username.equals( user.getUsername() ) && !isTokenExpired( token ));
}
/**
* 獲取token是否過期
*/
public Boolean isTokenExpired(String token) {
Date expiration = getExpirationDateFromToken( token );
return expiration.before( new Date() );
}
/**
* 根據token獲取username
*/
public String getUsernameFromToken(String token) {
String username = getClaimsFromToken( token ).getSubject();
return username;
}
/**
* 獲取token的過期時間
*/
public Date getExpirationDateFromToken(String token) {
Date expiration = getClaimsFromToken( token ).getExpiration();
return expiration;
}
/**
* 解析JWT
*/
private Claims getClaimsFromToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey( SECRET )
.parseClaimsJws( token )
.getBody();
return claims;
}
}
JwtTokenFilter
@Component
public class JwtTokenFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
/**
* 存放Token的Header Key
*/
public static final String HEADER_STRING = "Authorization";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String token = request.getHeader( HEADER_STRING );
if (null != token) {
String username = jwtTokenUtil.getUsernameFromToken(token);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
}
}
AuthServiceImpl
@Service
public class AuthServiceImpl implements AuthService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
public String login(String username, String password) {
UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken( username, password );
Authentication authentication = authenticationManager.authenticate(upToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = userDetailsService.loadUserByUsername( username );
String token = jwtTokenUtil.generateToken(userDetails);
return token;
}
}
關鍵代碼就是這些,其他類代碼參照後面提供的源碼地址。
驗證
登錄,獲取token
curl -X POST -d "username=admin&password=123456" http://127.0.0.1:8080/auth/login
返回
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTU1NDQ1MzUwMX0.sglVeqnDGUL9pH1oP3Lh9XrdzJIS42VKBApd2nPJt7e1TKhCEY7AUfIXnzG9vc885_jTq4-h8R6YCtRRJzl8fQ
不帶token訪問資源
curl -X POST -d "name=zhangsan" http://127.0.0.1:8080/admin/hi
返回,拒絕訪問
{
"timestamp": "2019-03-31T08:50:55.894+0000",
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/auth/login"
}
攜帶token訪問資源
curl -X POST -H "Authorization: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTU1NDQ1MzUwMX0.sglVeqnDGUL9pH1oP3Lh9XrdzJIS42VKBApd2nPJt7e1TKhCEY7AUfIXnzG9vc885_jTq4-h8R6YCtRRJzl8fQ" -d "name=zhangsan" http://127.0.0.1:8080/admin/hi
返回正確
hi zhangsan , you have 'admin' role
源碼
https://github.com/gf-huanchupk/SpringBootLearning/tree/master/springboot-jwt
推薦閱讀
Spring Boot 系列精選
Spring Boot 自定義 starter
Spring Boot 整合 mybatis-plus
Spring Boot 整合 spring cache
Spring Boot 整合 rabbitmq
Spring Boot 整合 elasticsearch
Spring Boot 整合 docker
Spring Boot 整合 elk
Spring Boot Admin 2.0 詳解
Spring Boot 整合 apollo
Spring Boot Security 詳解
Spring Boot Security 整合 OAuth2 設計安全API介面服務
Spring Boot Security 整合 JWT 實現 無狀態的分散式API介面
SpringCloud 系列精選
Spring Cloud Gateway 入門
Spring Cloud Gateway 之 Predict
Spring Cloud Gateway 之 Filter
Spring Cloud Gateway 之 限流
Spring Cloud Gateway 之 服務註冊與發現
Dubbo 系列精選
Dubbo 搭建管理控制台
Dubbo 創建 提供者 消費者
Dubbo 配置
Dubbo 高可用
Docker 系列精選
服務 Docker 化
Docker 私有倉庫搭建
DockerSwarm 集群環境搭建
DockerSwarm 微服務部署
歡迎掃碼或微信搜索公眾號《程式員果果》關註我,關註有驚喜~