Spring Boot 鉴权之—— JWT 鉴权

第一:什么是JWT鉴权

       1. JWT即JSON Web Tokens,是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519),他可以用来安全的传递信息,因为传递的信息是经过加密算法加密过得。

       2.JWT常用的加密算法有:HMAC算法或者是RSA的公私秘钥对进行签名,也可以使用公钥/私钥的非对称算法

       3.JWT的使用场景主要包括:

              1) 认证授权,特别适用于分布式站点的单点登录(SSO)场景,只要用户开放的登录入口登录过一次系统,就会返回一个token,之后的请求都需要包含token。

         2)交换信息,通过使用密钥对来安全的传送信息,可以知道发送者是谁、放置消息是否被篡改,一般被用来在身份提供者和服务提供者之间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,例如:设备信息,版本号等,该token也可直接被用于认证,也可被加密。

第二:JWT构成

        JSON Web Tokens(JWT)有三部分构成,用英文句点分割(.) ,一般看起来例如:xxxxx.yyyyy.zzzzz

        分为:

                 Header  头信息

                 Payload  荷载信息,实际数据

                 Signature  由头信息+荷载信息+密钥 组合之后进行加密得到

  1) Header 头信息通常包含两部分,type:代表token的类型,这里使用的是JWT类型。 alg:代表使用的算法,例如HMAC SHA256或RSA.

           {

              "alg": "HS256",

              "typ": "JWT"

           } // 这会被经过base64Url编码形成第一部分

    2)Payload 一个token的第二部分是荷载信息,它包含一些声明Claim(实体的描述,例:用户信息和其他的一些元数据)

        声明分三类:

           1)Reserved Claims,这是一套预定义的声明,并不是必须的,这是一套易于使用、操作性强的声明。包括:iss(issuer)、exp(expiration time)、sub(subject)、aud(audience)等

           2)Plubic Claims,

           3)Private Claims,交换信息的双方自定义的声明

        {

                 "sub": "1234567890",

                 "name": "John Doe",

                 "iat": 1516239022

             }//同样经过Base64Url编码后形成第二部分

         

    3) signature  使用header中指定的算法将编码后的header、编码后的payload、一个secret进行加密

     例如使用的是HMAC SHA256算法,大致流程类似于: HMACSHA256base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

     这个signature字段被用来确认JWT信息的发送者是谁,并保证信息没有被修改

          HMACSHA256( 

               base64UrlEncode(header) + "." + 

               base64UrlEncode(payload),  

          your-256-bit-secret

            ) 

第三步:JWT认证流程

  

上图是官方提供的一个认证流程图 ,我们可以看到它的授权流程是:

       1.客户端通过post请求请求服务端登录认证接口

       2.服务端用秘密创建JWT

       3.服务端将JWT返回浏览器

       4.客户端在授权报头上发送JWT

       5.服务端检查JWT签名从JWT获取用户信息

       6.服务端向客户端发送响应

通常我们所看到的认证流程,只能看到第一步和第六步,如果使用调试模式或者用抓包工具抓取就可以看到完整流程。

 第四步:jwt使用

        1)、  引入相关jar:    

<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
       <groupId>io.jsonwebtoken</groupId>
       <artifactId>jjwt</artifactId>
      <version>0.7.0</version>
</dependency>

<!-- 使用lombok优雅的编码 -->
<dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
</dependency>

        2)、jwt只要编码

      JwtUtil:jwt工具类

import org.springframework.util.StringUtils;

/**
* jwt工具类
* @author zyl
*
*/
public class JwtUtils {
private static final String AUTHORIZATION_HEADER_PREFIX = "Bearer ";

/**
* 获取原始令牌
* remove 'Bearer ' string
*
* @param authorizationHeader
* @return
*/
public static String getRawToken(String authorizationHeader) {
return authorizationHeader.substring(AUTHORIZATION_HEADER_PREFIX.length());
}

/**
* 获取令牌头
* @param rawToken
* @return
*/
public static String getTokenHeader(String rawToken) {
return AUTHORIZATION_HEADER_PREFIX + rawToken;
}

/**
* 验证授权请求头
* @param authorizationHeader
* @return
*/
public static boolean validate(String authorizationHeader) {
return StringUtils.hasText(authorizationHeader) && authorizationHeader.startsWith(AUTHORIZATION_HEADER_PREFIX);
}

/**
* 获取授权头前缀
* @return
*/
public static String getAuthorizationHeaderPrefix() {
return AUTHORIZATION_HEADER_PREFIX;
}
}

JwtAuthenticationFilter  JWT认证过滤器

import com.example.demo.util.JwtUtils;
import io.jsonwebtoken.Jwts;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;

/**
* JWT认证过滤器
* @author zyl
*
*/
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {

public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String header = request.getHeader("Authorization");

if (header == null || !header.startsWith(JwtUtils.getAuthorizationHeaderPrefix())) {
chain.doFilter(request, response);
return;
}

UsernamePasswordAuthenticationToken authenticationToken = getUsernamePasswordAuthenticationToken(header);

SecurityContextHolder.getContext().setAuthentication(authenticationToken);
chain.doFilter(request, response);
}

private UsernamePasswordAuthenticationToken getUsernamePasswordAuthenticationToken(String token) {
String user = Jwts.parser()
.setSigningKey("PrivateSecret")
.parseClaimsJws(token.replace(JwtUtils.getAuthorizationHeaderPrefix(), ""))
.getBody()
.getSubject();

if (null != user) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}

return null;
}
}

 JwtLoginFilter  jwt登录过滤器

import com.example.demo.domain.Employee;
import com.example.demo.util.JwtUtils;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;

/**
* JWT 登录过滤器
* @author zyl
*
*/
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {

private AuthenticationManager authenticationManager;

public JwtLoginFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
Employee employee = new Employee();
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
employee.getUsername(),
employee.getPassword(),
new ArrayList<>()
)
);
}

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
String token = Jwts.builder()
.setSubject(((User) authResult.getPrincipal()).getUsername())
.setExpiration(new Date(System.currentTimeMillis() + 30 * 60 * 1000))
.signWith(SignatureAlgorithm.HS512, "PrivateSecret")
.compact();
response.addHeader("Authorization", JwtUtils.getTokenHeader(token));
}
}

 SecurityConfiguration   config配置

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import com.jwt.server.filter.JwtAuthenticationFilter;
import com.jwt.server.filter.JwtLoginFilter;

/**
* 通过SpringSecurity的配置,将JWTLoginFilter,JWTAuthenticationFilter组合在一起
*
* @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) 在springboot1.5.8的时候该注解是可以用的 具体看源码
* @author zyl
*
*/
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}

@Override
protected void configure(HttpSecurity http) throws Exception {

    

//自定义 默认
http.cors().and().csrf().disable().authorizeRequests()      .antMatchers("/user/login","/login", "/oauth/authorize").permitAll()
      .anyRequest().authenticated()
      .and()
      .requestMatchers().antMatchers("/user/login","/login","/oauth/authorize")
      .and()
      .addFilter(new JwtLoginFilter(authenticationManager()))//默认登录过滤器
      .addFilter(new JwtAuthenticationFilter(authenticationManager()));//自定义过滤器


}

}

UserInfo 认证用户 


import lombok.Data;

/**
* 认证用户
* @author zyl
*
*/
@Data
public class UserInfo {

private String id;
private String username;
private String password;

public UserInfo() {
this.setId("testId");
this.setUsername("testUsername");
this.setPassword("testPassword");
}
}

UserDetailServiceImpl:核心认证用户service类


import static java.util.Collections.emptyList;

import org.springframework.security.core.userdetails.User;
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.Service;

import com.jwt.server.domain.UserInfo;

/**
*
* @author zyl
*
*/
@Service
public class UserDetailServiceImpl implements UserDetailsService {

@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
UserInfo user = new UserInfo();
return new User(user.getUsername(), user.getPassword(), emptyList());
}
}

 第五步:测试

 请求登录:localhost:8085/login

这里测试自定义登录的是请用post方式,因为默认源码里边只支持post方式

自定义登录:localhost:8085/user/login

 源码地址:https://github.com/GitHubZhangCom/spring-security-oauth-example/

    注意:我在测试的时候发现springboot 2.0的版本不支持默认登录的匿名登录方式

 成功测试,是在1.5.8的版本。

这是在2.0.4版本的时候默认登录报的错:

 这里因时间原因,暂不做处理,后期有时间我会同步更新源码和博客。

猜你喜欢

转载自www.cnblogs.com/haoliyou/p/9606082.html