Spring Boot Security 結合 JWT 實現無狀態的分佈式API接口

JSON Web Token(縮寫 JWT)是目前最流行的跨域認證解決方案。這篇文章主要介紹了Spring Boot Security 結合 JWT 實現無狀態的分佈式API接口 ,需要的朋友可以參考下

簡介

JSON Web Token(縮寫 JWT)是目前最流行的跨域認證解決方案。JSON Web Token 入門教程 這篇文章可以幫你瞭解JWT的概念。本文重點講解Spring Boot 結合 jwt ,來實現前後端分離中,接口的安全調用。

Spring Security,這是一種基於 Spring AOP 和 Servlet 過濾器的安全框架。它提供全面的安全性解決方案,同時在 Web 請求級和方法調用級處理身份確認和授權。

快速上手

之前的文章已經對 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 Security 結合 JWT 實現無狀態的分佈式API接口,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對神馬文庫網站的支持!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章