Spring Boot+JWT+Spring Security實現受權認證保護Rest API

 

 

 

 

 

一般狀況下,把API直接暴露出去是風險很大的。那麼通常來講,對API要劃分出必定的權限級別,而後作一個用戶的鑑權,依據鑑權結果給予用戶開放對應的API。目前,比較主流的方案有幾種:css

  1. 用戶名和密碼鑑權,使用Session保存用戶鑑權結果。
  2. 使用OAuth進行鑑權(其實OAuth也是一種基於Token的鑑權,只是沒有規定Token的生成方式)
  3. 自行採用Token進行鑑權

這裏主要講一下JWThtml

JWT定義:web

JWT是 Json Web Token 的縮寫。它是基於 RFC 7519 標準定義的一種能夠安全傳輸的 小巧 和 自包含 的JSON對象。因爲數據是使用數字簽名的,因此是可信任的和安全的。算法

JWT可使用HMAC算法對secret進行加密或者使用RSA的公鑰私鑰對來進行簽名。spring

JWT的工做流程

下面是一個JWT的工做流程圖。模擬一下實際的流程是這樣的(假設受保護的API在/protected中)json

  1. 用戶導航到登陸頁,輸入用戶名、密碼,進行登陸
  2. 服務器驗證登陸鑑權,若是改用戶合法,根據用戶的信息和服務器的規則生成JWT Token
  3. 服務器將該token以json形式返回(不必定要json形式,這裏說的是一種常見的作法)
  4. 用戶獲得token,存在localStorage、cookie或其它數據存儲形式中。
  5. 之後用戶請求/protected中的API時,在請求的header中加入 Authorization: Bearer xxxx(token)。此處注意token以前有一個7字符長度的 Bearer
  6. 服務器端對此token進行檢驗,若是合法就解析其中內容,根據其擁有的權限和本身的業務邏輯給出對應的響應結果。
  7. 用戶取得結果

 

添加maven依賴:api

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.10.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
<dependencies>
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

    <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>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
<dependencies>

 JWT生成的代碼:服務器

static public String createAccessToken(Authentication auth) { AccountCredentials credentials = (AccountCredentials) auth.getDetails(); String userName = auth.getName(); String role = credentials.isAdmin() ? "ROLE_ADMIN" : "ROLE_USER"; Calendar nowTime = Calendar.getInstance(); nowTime.add(Calendar.MINUTE, TOKENEXPIRATIONTIME); String accessToken = Jwts.builder().claim("authorities", role).claim("username", userName) .claim("userid", credentials.getUserId()).setSubject(userName).setIssuer(TOKENISSUER) .setIssuedAt(new Date(System.currentTimeMillis())).setExpiration(nowTime.getTime()) .signWith(SignatureAlgorithm.HS512, SECRET).compact(); return accessToken; }

Security:cookie

入口過濾器

@Configuration @EnableWebSecurity @Order(2) public class WebSecurityConfig extends WebSecurityConfigurerAdapter{  @Value("${server.context-path}") String contextPath; @Override protected void configure(HttpSecurity http) throws Exception{ http.headers().xssProtection().xssProtectionEnabled(true); http.csrf().disable().exceptionHandling().authenticationEntryPoint(myEntryPoint()) .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and().authorizeRequests() .antMatchers(HttpMethod.OPTIONS).permitAll().antMatchers("/").permitAll().antMatchers("/images/**") .permitAll().antMatchers("/*.html").permitAll().antMatchers("/hello/**").permitAll() .antMatchers("/admin/add").permitAll().antMatchers("/admin/encode").permitAll() .antMatchers("/admin/delete").permitAll().antMatchers("/category/list").permitAll() .antMatchers("/module/list").permitAll().antMatchers("/photo/list").permitAll() .antMatchers("/music/list").permitAll().antMatchers("/doc/list").permitAll().antMatchers("/vr/list") .permitAll().antMatchers("/video/list").permitAll().antMatchers("/category/list/privateOpen") .permitAll().antMatchers("/photo/list/privateOpen").permitAll().antMatchers("/video/list/privateOpen") .permitAll().antMatchers("/music/list/privateOpen").permitAll().antMatchers("/doc/list/privateOpen") .permitAll().antMatchers("/vr/list/privateOpen").permitAll().antMatchers("/health/**").permitAll() .antMatchers("/favicon.ico").permitAll().antMatchers("**/*.html").permitAll().antMatchers("**/*.css") .permitAll().antMatchers("**/*.js").permitAll().antMatchers("/", "/*swagger*/**", "/v2/api-docs") .permitAll() .antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources", "/configuration/security", "/swagger-ui.html", "/webjars/**") .permitAll() // .antMatchers(HttpMethod.POST, "/logout").authenticated() // 全部 /login 的POST請求 都放行
        .antMatchers(HttpMethod.POST, "/login/**").permitAll() .antMatchers(HttpMethod.GET, "/token/**").permitAll() // 全部請求須要身份認證
 .anyRequest().authenticated().and() // 添加一個過濾器 全部訪問 /login 的請求交給 JWTLoginFilter 來處理 這個類處理全部的JWT相關內容
        .addFilterBefore(new JWTLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class) // 添加一個過濾器驗證其餘請求的Token是否合法
        .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 使用自定義身份驗證組件
        auth.authenticationProvider(new CustomAuthenticationProvider()); } @Bean AuthenticationEntryPoint myEntryPoint() { return new ExampleAuthenticationEntryPoint(); } }

JWT認證登陸、鑑權

 

public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter { public JWTLoginFilter(String url, AuthenticationManager authManager) { super(new AntPathRequestMatcher(url)); setAuthenticationManager(authManager); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse res) throws AuthenticationException, IOException, ServletException { try { AccountCredentials creds = new AccountCredentials(); if (creds == null || StringUtils.isEmpty(creds.getUsername()) || StringUtils.isEmpty(creds.getPassword())) { String result = JSONResult.fillResultString(HttpServletResponse.SC_BAD_REQUEST, "請求參數無效", null); res.setContentType("application/json;charset=UTF-8"); res.getWriter().println(result); } return getAuthenticationManager() .authenticate(new UsernamePasswordAuthenticationToken(creds.getUsername(), creds.getPassword())); } catch (JsonMappingException ex) { String result = JSONResult.fillResultString(1, "[參數異常]", null); res.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); res.getWriter().println(result); res.getWriter().close(); } catch (Exception e) { e.printStackTrace(); String result = JSONResult.fillResultString(10006, "鑑權失敗,請從新登陸", null); res.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); res.getWriter().println(result); res.getWriter().close(); } return null; } @Override protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException {  TokenAuthenticationService.addAuthentication(res, auth);//響應返回JWT Token
 clearAuthenticationAttributes(req); } @Override protected void unsuccessfulAuthentication(HttpServletRequest req, HttpServletResponse res, AuthenticationException failed) throws IOException, ServletException { SecurityContextHolder.clearContext(); res.setContentType("application/json;charset=UTF-8"); res.setStatus(HttpServletResponse.SC_OK); String result = JSONResult.fillResultString(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal Server Error!!", null); res.getWriter().println(result); res.getWriter().close(); } protected final void clearAuthenticationAttributes(HttpServletRequest req) { HttpSession session = req.getSession(false); if(session == null){ return; } session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); } }

認證用戶名和密碼:

@Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 獲取認證的用戶名 & 密碼
        String userName = authentication.getName(); String passWord = authentication.getCredentials().toString(); if (loginService == null) { loginService = SpringContextUtil.getBean("loginService"); } HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest(); String appId = request.getHeader("appId"); AccountCredentials user = loginService.login(userName, passWord,appId); // 認證邏輯
        if (user != null) { // // 這裏設置權限和角色 // ArrayList<GrantedAuthority> authorities = new ArrayList<>(); // authorities.add( new GrantedAuthorityImpl("ROLE_ADMIN") ); // // authorities.add( new GrantedAuthorityImpl("AUTH_WRITE") ); // // 生成令牌 // Authentication auth = new UsernamePasswordAuthenticationToken(name, password, // authorities); // 生成令牌
            UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userName, passWord); auth.setDetails(user); return auth; } else { throw new BadCredentialsException("密碼錯誤~"); } }

demo源碼下載連接:https://pan.baidu.com/s/1kWYeBUR,密碼:plr4

相關文章
相關標籤/搜索