扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
简介

龙泉ssl适用于网站、小程序/APP、API接口等需要进行数据传输应用场景,ssl证书未来市场广阔!成为成都创新互联的ssl证书销售渠道,可以享受市场价格4-6折优惠!如果有意向欢迎电话联系或者加微信:18982081108(备注:SSL证书合作)期待与您的合作!
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
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-security io.jsonwebtoken jjwt 0.9.0 MySQL mysql-connector-java runtime org.mybatis.spring.boot mybatis-spring-boot-starter 2.0.0 
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 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接口,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对创新互联网站的支持!

我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流