第一:什麼是JWT鑑權html
1. JWT即JSON Web Tokens,是爲了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519),他能夠用來安全的傳遞信息,由於傳遞的信息是通過加密算法加密過得。java
2.JWT經常使用的加密算法有:HMAC算法或者是RSA的公私祕鑰對進行簽名,也可使用公鑰/私鑰的非對稱算法git
3.JWT的使用場景主要包括:github
1) 認證受權,特別適用於分佈式站點的單點登陸(SSO)場景,只要用戶開放的登陸入口登陸過一次系統,就會返回一個token,以後的請求都須要包含token。web
2)交換信息,經過使用密鑰對來安全的傳送信息,能夠知道發送者是誰、放置消息是否被篡改,通常被用來在身份提供者和服務提供者之間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也能夠增長一些額外的其它業務邏輯所必須的聲明信息,例如:設備信息,版本號等,該token也可直接被用於認證,也可被加密。算法
第二:JWT構成spring
JSON Web Tokens(JWT)有三部分構成,用英文句點分割(.) ,通常看起來例如:xxxxx.yyyyy.zzzzz編程
分爲:json
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算法,大體流程相似於: HMACSHA256( base64UrlEncode(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使用
源碼地址:
github: https://github.com/GitHubZhangCom/spring-security-oauth-example/
碼雲:https://gitee.com/region/spring-security-oauth-example/tree/master/spring-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
import java.io.IOException; import java.util.ArrayList; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; 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 com.jwt.server.util.JwtUtils; import io.jsonwebtoken.Jwts; /** * 自定義JWT認證過濾器 * 該類繼承自BasicAuthenticationFilter,在doFilterInternal方法中, * 從http頭的Authorization 項讀取token數據,而後用Jwts包提供的方法校驗token的合法性。 * 若是校驗經過,就認爲這是一個取得受權的合法請求 * @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
import java.io.IOException; import java.util.ArrayList; import java.util.Date; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; 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 com.fasterxml.jackson.databind.ObjectMapper; import com.jwt.server.domain.UserInfo; import com.jwt.server.util.JwtUtils; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; /** * 驗證用戶名密碼正確後,生成一個token,並將token返回給客戶端 * 該類繼承自UsernamePasswordAuthenticationFilter,重寫了其中的2個方法 attemptAuthentication * :接收並解析用戶憑證。 successfulAuthentication :用戶成功登陸後,這個方法會被調用,咱們在這個方法裏生成token。 * */ 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 { try { UserInfo user = new ObjectMapper().readValue(request.getInputStream(), UserInfo.class); return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>())); } catch (IOException e) { throw new RuntimeException(e); } } // 用戶成功登陸後,這個方法會被調用,咱們在這個方法裏生成token @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