在spring boot api上使用jwt認(rèn)證

本文翻譯自:
https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/
的前半部分榆浓,也就是關(guān)于在spring boot中使用jwt的部分

我會(huì)在之后把自己的程序上傳,以及在寫一篇關(guān)于這個(gè)的解釋,但是首先我們要把原文看一遍胡诗。

譯文:

在本博客文章中, 我們將學(xué)習(xí)如何處理使用 Spring boot編寫的 rest api 的身份驗(yàn)證和授權(quán)居暖。我們將從 GitHub 中克隆一個(gè)簡(jiǎn)單的 spring boot應(yīng)用程序, 它公開(kāi)了公共端點(diǎn), 然后我們將使用 spring security和 JWT 來(lái)保護(hù)這些端點(diǎn)。

使用JWT來(lái)保衛(wèi)RESTful API的安全(譯者注:JWTs是JWT的復(fù)數(shù))

JSON Web token(通常稱為 JWT) 是用于對(duì)應(yīng)用程序上的用戶進(jìn)行身份驗(yàn)證的標(biāo)記菱蔬。這一技術(shù)在過(guò)去幾年中獲得了普及, 因?yàn)樗购笈_(tái)能夠通過(guò)驗(yàn)證這些 JWTS 的內(nèi)容來(lái)接受請(qǐng)求。也就是說(shuō), 使用 JWTS 的應(yīng)用程序不再需要保存有關(guān)其用戶的 cookie 或其他session數(shù)據(jù)荒辕。此特性便于可伸縮性, 同時(shí)保證應(yīng)用程序的安全汗销。

在身份驗(yàn)證過(guò)程中, 當(dāng)用戶使用其憑據(jù)成功登錄時(shí), 將返回 JSON Web token, 并且必須在本地保存 (通常在本地存儲(chǔ)中)。每當(dāng)用戶要訪問(wèn)受保護(hù)的路由或資源 (端點(diǎn)) 時(shí), 用戶代理(user agent)必須連同請(qǐng)求一起發(fā)送 JWT, 通常在授權(quán)標(biāo)頭中使用Bearer schema抵窒。

當(dāng)后端服務(wù)器接收到帶有 JWT 的請(qǐng)求時(shí), 首先要做的是驗(yàn)證token弛针。這由一系列步驟組成, 如果其中任何一個(gè)失敗, 則必須拒絕該請(qǐng)求。以下列表顯示了所需的驗(yàn)證步驟:

  • 檢查 JWT 格式
  • 檢查簽名
  • 驗(yàn)證標(biāo)準(zhǔn)請(qǐng)求
  • 檢查客戶端權(quán)限 (范圍)

在本文中, 我們不會(huì)深入討論 JWTS 的具體細(xì)節(jié), 但是, 如果需要, 這里提供了更多關(guān)于JWTS的東西JWT認(rèn)證.

RESTful Spring Boot API

我們要保護(hù)的restful Spring boot API 是一個(gè)任務(wù)列表管理器李皇。任務(wù)列表是全局保留的, 這意味著所有用戶都將看到并與同一列表進(jìn)行交互削茁。要克隆并運(yùn)行此應(yīng)用程序, 讓我們使用以下命令(譯者注:此處使用到了git):

# clone the starter project
git clone https://github.com/auth0-blog/spring-boot-auth.git

cd spring-boot-auth

# run the unsecured RESTful API

gradle bootRun

如果一切正常工作, 我們r(jià)estful spring boot API 將啟動(dòng)和運(yùn)行。為了測(cè)試它, 我們可以使用像Postman 或者curl這樣的工具來(lái)向可用端點(diǎn)發(fā)出請(qǐng)求(譯者注:原文作者這里用的是curl):

# issue a GET request to see the (empty) list of tasks
curl http://localhost:8080/tasks

# issue a POST request to create a new task
curl -H "Content-Type: application/json" -X POST -d '{
    "description": "Buy some milk(shake)"
}'  http://localhost:8080/tasks

# issue a PUT request to update the recently created task
curl -H "Content-Type: application/json" -X PUT -d '{
    "description": "Buy some milk"
}'  http://localhost:8080/tasks/1

# issue a DELETE request to remove the existing task
curl -X DELETE http://localhost:8080/tasks/1

上面的命令中使用的所有端點(diǎn)都在 TaskController 類中定義, 屬于 com.auth0.samples.authapi.task包掉房。除此類外, 此包還包含另外兩個(gè)類:

  • Task: 表示應(yīng)用程序中的任務(wù)的實(shí)體模型茧跋。
  • TaskRepository: 負(fù)責(zé)處理任務(wù)持久性的類。

我們的應(yīng)用程序的持久性層由一個(gè)名為 HSQLDB 的內(nèi)存中數(shù)據(jù)庫(kù)支持卓囚。我們通常會(huì)在實(shí)際的應(yīng)用程序中使用像 PostgreSQL 或 MySQL 這樣的生產(chǎn)數(shù)據(jù)庫(kù), 但是對(duì)于本教程, 這個(gè)內(nèi)存中的數(shù)據(jù)庫(kù)將足夠使用

在Spring Boot APIs上啟用用戶注冊(cè)

現(xiàn)在, 我們看了一下我們r(jià)estful Spring boot API 暴露的端點(diǎn), 我們將開(kāi)始保護(hù)它瘾杭。第一步是允許新用戶注冊(cè)自己。我們將在此功能中創(chuàng)建的類將屬于一個(gè)叫做 com.auth0.samples.authapi.user的新包哪亿。讓我們創(chuàng)建這個(gè)包并添加一個(gè)名為 ApplicationUser 的新實(shí)體類

package com.auth0.samples.authapi.user;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class ApplicationUser {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String username;
    private String password;

    public long getId() {
        return id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

這個(gè)實(shí)體類包含三個(gè)屬性:

  • id 作為應(yīng)用程序中用戶實(shí)例的主要標(biāo)識(shí)符
  • username 用戶用來(lái)識(shí)別自己的用戶名
  • password 用于檢查用戶標(biāo)識(shí)的密碼

要管理此實(shí)體的持久性層, 我們將創(chuàng)建一個(gè)稱為 ApplicationUserRepository 的接口粥烁。此接口是 JpaRepository 的擴(kuò)展, 它使我們能夠訪問(wèn)一些常用的方法 (如 save), 并將在 ApplicationUser 類的同一包中創(chuàng)建:

package com.auth0.samples.authapi.user;

import org.springframework.data.jpa.repository.JpaRepository;

public interface ApplicationUserRepository extends JpaRepository<ApplicationUser, Long> {
    ApplicationUser findByUsername(String username);
}

我們還在這個(gè)接口上添加了一個(gè)稱為 findByUsername 的方法。當(dāng)我們實(shí)現(xiàn)身份驗(yàn)證功能時(shí), 將使用此方法蝇棉。

允許新用戶注冊(cè)的端點(diǎn)將由新的 @Controller 類處理讨阻。我們將調(diào)用控制器 UserController 并將其添加到與 ApplicationUser 類相同的包中:

package com.auth0.samples.authapi.user;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/users")
public class UserController {

    private ApplicationUserRepository applicationUserRepository;
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    public UserController(ApplicationUserRepository applicationUserRepository,
                          BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.applicationUserRepository = applicationUserRepository;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @PostMapping("/sign-up")
    public void signUp(@RequestBody ApplicationUser user) {
        user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
        applicationUserRepository.save(user);
    }
}

端點(diǎn)的實(shí)現(xiàn)非常簡(jiǎn)單。它所做的只是對(duì)新用戶的密碼進(jìn)行加密 (以純文本形式保存并不是一個(gè)好主意), 然后將其保存到數(shù)據(jù)庫(kù)中篡殷。加密過(guò)程由 BCryptPasswordEncoder 的實(shí)例處理, 它是屬于 Spring 安全框架的類钝吮。

現(xiàn)在我們的應(yīng)用程序有兩個(gè)空白:

  1. 我們沒(méi)有將 Spring 安全框架添加到項(xiàng)目的依賴。
  2. 沒(méi)有可在 UserController 類中插入的 BCryptPasswordEncoder 的默認(rèn)實(shí)例。

我們通過(guò)將 Spring 安全框架的依賴項(xiàng)添加到. gradle 文件中來(lái)解決第一個(gè)問(wèn)題:奇瘦、

dependencies {
    ...
    compile("org.springframework.boot:spring-boot-starter-security")
}

第二個(gè)問(wèn)題, 缺少的 BCryptPasswordEncoder 實(shí)例, 我們通過(guò)實(shí)現(xiàn)一個(gè)生成 BCryptPasswordEncoder 實(shí)例的方法來(lái)解決棘催。此方法必須用 @Bean 批注, 我們將在Application類中添加它:

package com.auth0.samples.authapi;

// ... other imports
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@SpringBootApplication
public class Application {

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // ... main method definition
}

這就結(jié)束了用戶注冊(cè)功能, 但我們?nèi)匀蝗狈?duì)用戶身份驗(yàn)證和授權(quán)的支持。接下來(lái)我們來(lái)處理這些功能链患。

spring boot的用戶身份驗(yàn)證和授權(quán)

為了支持我們的應(yīng)用程序中的身份驗(yàn)證和授權(quán), 我們將:

  • 實(shí)現(xiàn)身份驗(yàn)證filter, 以便向發(fā)送憑據(jù)的用戶發(fā)出 JWTS
  • 實(shí)現(xiàn)授權(quán)filter以驗(yàn)證包含 JWTS 的請(qǐng)求
  • 創(chuàng)建 UserDetailsService 的自定義實(shí)現(xiàn), 以幫助 Spring security在框架中加載用戶特定的數(shù)據(jù)
  • 擴(kuò)展 WebSecurityConfigurerAdapter 類, 以根據(jù)需要自定義安全框架

在繼續(xù)開(kāi)發(fā)這些filter和類之前, 讓我們創(chuàng)建一個(gè)名為 com.auth0.samples.authapi.security 的新包巧鸭。這個(gè)包將保存所有與我們的應(yīng)用程序的安全性相關(guān)的代碼。

身份驗(yàn)證filter

我們要?jiǎng)?chuàng)建的第一個(gè)元素是負(fù)責(zé)身份驗(yàn)證過(guò)程的類麻捻。我們將調(diào)用此類 JWTAuthenticationFilter, 我們將使用以下代碼實(shí)現(xiàn)它:

package com.auth0.samples.authapi.security;

import com.auth0.samples.authapi.user.ApplicationUser;
import com.fasterxml.jackson.databind.ObjectMapper;
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;

import static com.auth0.samples.authapi.security.SecurityConstants.EXPIRATION_TIME;
import static com.auth0.samples.authapi.security.SecurityConstants.HEADER_STRING;
import static com.auth0.samples.authapi.security.SecurityConstants.SECRET;
import static com.auth0.samples.authapi.security.SecurityConstants.TOKEN_PREFIX;

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private AuthenticationManager authenticationManager;

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

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req,
                                                HttpServletResponse res) throws AuthenticationException {
        try {
            ApplicationUser creds = new ObjectMapper()
                    .readValue(req.getInputStream(), ApplicationUser.class);

            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            creds.getUsername(),
                            creds.getPassword(),
                            new ArrayList<>())
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest req,
                                            HttpServletResponse res,
                                            FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {

        String token = Jwts.builder()
                .setSubject(((User) auth.getPrincipal()).getUsername())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS512, SECRET.getBytes())
                .compact();
        res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
    }
}

請(qǐng)注意, 我們創(chuàng)建的身份驗(yàn)證filter擴(kuò)展了 UsernamePasswordAuthenticationFilter 類纲仍。當(dāng)我們?cè)?Spring security中添加新的filter時(shí), 我們可以明確地定義它filter鏈中的位置, 也可以讓框架自行計(jì)算出來(lái)。通過(guò)擴(kuò)展安全框架中提供的篩選器, Spring 可以自動(dòng)確定將其放在安全鏈中的最佳位置贸毕。

我們的自定義身份驗(yàn)證filter覆蓋基類的兩種方法:

  • attemptAuthentication: 我們?cè)谶@里分析用戶的憑據(jù)并將其發(fā)給 AuthenticationManager
  • successfulAuthentication: 這是用戶成功登錄時(shí)調(diào)用的方法郑叠。我們使用此方法為該用戶生成 JWT。

我們的 IDE 可能會(huì)抱怨此類中的代碼, 原因有兩個(gè)明棍。首先, 代碼從我們尚未創(chuàng)建的類SecurityConstants中導(dǎo)入了四個(gè)常量乡革。第二, 因?yàn)榇祟愒诿麨?JWTS 的類的幫助下生成 JWTS, 它屬于我們沒(méi)有添加到項(xiàng)目的依賴項(xiàng)的庫(kù)。

讓我們先解決缺少的依賴關(guān)系摊腋。在build. gradle 文件中, 讓我們添加以下代碼行:

dependencies {
    ...
    compile("io.jsonwebtoken:jjwt:0.7.0")
}

這將向我們的項(xiàng)目中添加 java JWT: 用于 java 和 Android 庫(kù)的 JSON Web token, 并將解決丟失的類的問(wèn)題》邪妫現(xiàn)在, 我們必須創(chuàng)建 SecurityConstants 類:

package com.auth0.samples.authapi.security;

public class SecurityConstants {
    public static final String SECRET = "SecretKeyToGenJWTs";
    public static final long EXPIRATION_TIME = 864_000_000; // 10 days
    public static final String TOKEN_PREFIX = "Bearer ";
    public static final String HEADER_STRING = "Authorization";
    public static final String SIGN_UP_URL = "/users/sign-up";
}

此類包含由 JWTAuthenticationFilter 類引用的所有四個(gè)常量, 以及稍后將使用的 SIGN_UP_URL 常量。

授權(quán) Filter

由于我們已經(jīng)實(shí)現(xiàn)了負(fù)責(zé)對(duì)用戶進(jìn)行身份驗(yàn)證的filter, 因此我們現(xiàn)在需要實(shí)現(xiàn)負(fù)責(zé)用戶授權(quán)的filter兴蒸。我們將此filter作為一個(gè)新類 (稱為 JWTAuthorizationFilter) 在 com.auth0.samples.authapi.security 包中創(chuàng)建:

package com.auth0.samples.authapi.security;

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;

import static com.auth0.samples.authapi.security.SecurityConstants.HEADER_STRING;
import static com.auth0.samples.authapi.security.SecurityConstants.SECRET;
import static com.auth0.samples.authapi.security.SecurityConstants.TOKEN_PREFIX;

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

    public JWTAuthorizationFilter(AuthenticationManager authManager) {
        super(authManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req,
                                    HttpServletResponse res,
                                    FilterChain chain) throws IOException, ServletException {
        String header = req.getHeader(HEADER_STRING);

        if (header == null || !header.startsWith(TOKEN_PREFIX)) {
            chain.doFilter(req, res);
            return;
        }

        UsernamePasswordAuthenticationToken authentication = getAuthentication(req);

        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(req, res);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        String token = request.getHeader(HEADER_STRING);
        if (token != null) {
            // parse the token.
            String user = Jwts.parser()
                    .setSigningKey(SECRET.getBytes())
                    .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                    .getBody()
                    .getSubject();

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

我們已經(jīng)擴(kuò)展了 BasicAuthenticationFilter, 使spring在filter鏈中替換為我們的自定義實(shí)現(xiàn)视粮。我們已經(jīng)實(shí)現(xiàn)的filter中最重要的部分是私有 的getAuthentication 方法。此方法從Authorization 標(biāo)頭中讀取 JWT, 然后使用 Jwts 驗(yàn)證令牌橙凳。如果一切就緒, 我們會(huì)在 SecurityContext 中設(shè)置用戶, 并允許請(qǐng)求繼續(xù)進(jìn)行蕾殴。

在spring boot中集成Security Filters

現(xiàn)在, 我們已經(jīng)正確創(chuàng)建了兩個(gè)安全filter, 我們必須在 Spring Security filter 鏈上配置它們。為此, 我們將在 com.auth0.samples.authapi.security 包中創(chuàng)建一個(gè)名為 WebSecurity 的新類:

package com.auth0.samples.authapi.security;

import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.context.annotation.Bean;

import static com.auth0.samples.authapi.security.SecurityConstants.SIGN_UP_URL;

@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
    private UserDetailsService userDetailsService;
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    public WebSecurity(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.userDetailsService = userDetailsService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable().authorizeRequests()
                .antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                // this disables session creation on Spring Security
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
    }

  @Bean
  CorsConfigurationSource corsConfigurationSource() {
    final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
    return source;
  }
}

我們用 @EnableWebSecurity 對(duì)此類進(jìn)行了注釋, 并使其擴(kuò)展 WebSecurityConfigurerAdapter 以利用 Spring security提供的默認(rèn) web 安全配置岛啸。這使我們可以通過(guò)定義三個(gè)方法來(lái)微調(diào)框架以滿足我們的需要:

  • configure(HttpSecurity http): 一種我們可以定義哪些資源是公共的, 哪些是受保護(hù)的的方法钓觉。在我們的例子中, 我們將 SIGN_UP_URL 端點(diǎn)設(shè)置為公共的, 其他所有內(nèi)容都是受保護(hù)的。我們還通過(guò) http.cors() 配置 CORS (跨源資源共享Cross-Origin Resource Sharing) 支持, 并在 Spring Security filter 鏈中添加自定義安全filter坚踩。
  • configure(AuthenticationManagerBuilder auth): 一種我們定義了 UserDetailsService 的自定義實(shí)現(xiàn)的方法, 以便在安全框架中加載用戶特定的數(shù)據(jù)荡灾。我們還使用此方法來(lái)設(shè)置應(yīng)用程序使用的加密方法 (BCryptPasswordEncoder)。
  • corsConfigurationSource (): 一種可以允許/限制我們的 CORS 支持的方法瞬铸。在我們的情況下, 我們把它公開(kāi), 允許來(lái)自任何來(lái)源的請(qǐng)求 (**)卧晓。

spring security不附帶一個(gè)具體的 UserDetailsService實(shí)現(xiàn), 我們可以使用我們的內(nèi)存中的數(shù)據(jù)庫(kù)。因此, 我們?cè)?com.auth0.samples.authapi.user包中創(chuàng)建一個(gè)名為 UserDetailsServiceImpl 的新類來(lái)提供一個(gè):

package com.auth0.samples.authapi.user;

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 static java.util.Collections.emptyList;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    private ApplicationUserRepository applicationUserRepository;

    public UserDetailsServiceImpl(ApplicationUserRepository applicationUserRepository) {
        this.applicationUserRepository = applicationUserRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        ApplicationUser applicationUser = applicationUserRepository.findByUsername(username);
        if (applicationUser == null) {
            throw new UsernameNotFoundException(username);
        }
        return new User(applicationUser.getUsername(), applicationUser.getPassword(), emptyList());
    }
}

我們必須實(shí)現(xiàn)的唯一方法是 loadUserByUsername赴捞。當(dāng)用戶嘗試進(jìn)行身份驗(yàn)證時(shí), 此方法接收用戶名, 在數(shù)據(jù)庫(kù)中搜索包含該名稱的記錄, 如果找到, 則返回用戶實(shí)例。然后, 根據(jù)用戶在登錄請(qǐng)求中傳遞的憑據(jù)檢查此實(shí)例的屬性 (用戶名和密碼)郁稍。最后一個(gè)過(guò)程是通過(guò) Spring 安全框架在這個(gè)類之外執(zhí)行的赦政。

我們現(xiàn)在可以放心了, 我們的端點(diǎn)不會(huì)公開(kāi)暴露, 我們可以支持與 JWTS 在spring boot中正確的認(rèn)證和授權(quán)。要檢查所有內(nèi)容, 請(qǐng)運(yùn)行我們的應(yīng)用程序 (通過(guò) IDE 或通過(guò) gradle bootRun) 并發(fā)出以下請(qǐng)求:

# issues a GET request to retrieve tasks with no JWT
# HTTP 403 Forbidden status is expected
curl http://localhost:8080/tasks

# registers a new user
curl -H "Content-Type: application/json" -X POST -d '{
    "username": "admin",
    "password": "password"
}' http://localhost:8080/users/sign-up

# logs into the application (JWT is generated)
curl -i -H "Content-Type: application/json" -X POST -d '{
    "username": "admin",
    "password": "password"
}' http://localhost:8080/login

# issue a POST request, passing the JWT, to create a task
# remember to replace xxx.yyy.zzz with the JWT retrieved above
curl -H "Content-Type: application/json" \
-H "Authorization: Bearer xxx.yyy.zzz" \
-X POST -d '{
    "description": "Buy watermelon"
}'  http://localhost:8080/tasks

# issue a new GET request, passing the JWT
# remember to replace xxx.yyy.zzz with the JWT retrieved above
curl -H "Authorization: Bearer xxx.yyy.zzz" http://localhost:8080/tasks

翻譯到此結(jié)束,原文后面還有使用Auth0的一些內(nèi)容恢着,與本文無(wú)關(guān)桐愉,故不做翻譯。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末掰派,一起剝皮案震驚了整個(gè)濱河市从诲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌靡羡,老刑警劉巖系洛,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異略步,居然都是意外死亡描扯,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門趟薄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)绽诚,“玉大人,你說(shuō)我怎么就攤上這事杭煎《鞴唬” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵羡铲,是天一觀的道長(zhǎng)蜂桶。 經(jīng)常有香客問(wèn)我,道長(zhǎng)犀勒,這世上最難降的妖魔是什么屎飘? 我笑而不...
    開(kāi)封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮贾费,結(jié)果婚禮上钦购,老公的妹妹穿的比我還像新娘。我一直安慰自己褂萧,他們只是感情好押桃,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著导犹,像睡著了一般唱凯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谎痢,一...
    開(kāi)封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天磕昼,我揣著相機(jī)與錄音,去河邊找鬼节猿。 笑死票从,一個(gè)胖子當(dāng)著我的面吹牛漫雕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播峰鄙,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼浸间,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了吟榴?” 一聲冷哼從身側(cè)響起魁蒜,我...
    開(kāi)封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吩翻,沒(méi)想到半個(gè)月后兜看,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仿野,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年铣减,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脚作。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡葫哗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出球涛,到底是詐尸還是另有隱情劣针,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布亿扁,位于F島的核電站捺典,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏从祝。R本人自食惡果不足惜襟己,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望牍陌。 院中可真熱鬧擎浴,春花似錦、人聲如沸毒涧。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)契讲。三九已至仿吞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捡偏,已是汗流浹背唤冈。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留银伟,地道東北人你虹。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓凉当,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親售葡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容