您好,登錄后才能下訂單哦!
通常情況下,把API直接暴露出去是風(fēng)險很大的,不說別的,直接被機器攻擊就喝一壺的。那么一般來說,對API要劃分出一定的權(quán)限級別,然后做一個用戶的鑒權(quán),依據(jù)鑒權(quán)結(jié)果給予用戶開放對應(yīng)的API。目前,比較主流的方案有幾種:
第一種就不介紹了,由于依賴Session來維護狀態(tài),也不太適合移動時代,新的項目就不要采用了。第二種OAuth的方案和JWT都是基于Token的,但OAuth其實對于不做開放平臺的公司有些過于復(fù)雜。我們主要介紹第三種:JWT。
什么是JWT?
JWT是 Json Web Token 的縮寫。它是基于 RFC 7519 標(biāo)準(zhǔn)定義的一種可以安全傳輸?shù)?小巧 和 自包含 的JSON對象。由于數(shù)據(jù)是使用數(shù)字簽名的,所以是可信任的和安全的。JWT可以使用HMAC算法對secret進行加密或者使用RSA的公鑰私鑰對來進行簽名。
JWT的工作流程
下面是一個JWT的工作流程圖。模擬一下實際的流程是這樣的(假設(shè)受保護的API在/protected中)
JWT工作流程圖
為了更好的理解這個token是什么,我們先來看一個token生成后的樣子,下面那坨亂糟糟的就是了。
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ3YW5nIiwiY3JlYXRlZCI6MTQ4OTA3OTk4MTM5MywiZXhwIjoxNDg5Njg0NzgxfQ.RC-BYCe_UZ2URtWddUpWXIp4NMsoeq2O6UF-8tVplqXY1-CI9u1-a-9DAAJGfNWkHE81mpnR3gXzfrBAB3WUAg
但仔細看到的話還是可以看到這個token分成了三部分,每部分用 . 分隔,每段都是用 Base64 編碼的。如果我們用一個Base64的解碼器的話 ( https://www.base64decode.org/ ),可以看到第一部分 eyJhbGciOiJIUzUxMiJ9 被解析成了:
{ "alg":"HS512" }
這是告訴我們HMAC采用HS512算法對JWT進行的簽名。
第二部分 eyJzdWIiOiJ3YW5nIiwiY3JlYXRlZCI6MTQ4OTA3OTk4MTM5MywiZXhwIjoxNDg5Njg0NzgxfQ 被解碼之后是
{ "sub":"wang", "created":1489079981393, "exp":1489684781 }
這段告訴我們這個Token中含有的數(shù)據(jù)聲明(Claim),這個例子里面有三個聲明:sub, created
和 exp
。在我們這個例子中,分別代表著用戶名、創(chuàng)建時間和過期時間,當(dāng)然你可以把任意數(shù)據(jù)聲明在這里。
看到這里,你可能會想這是個什么鬼token,所有信息都透明啊,安全怎么保障?別急,我們看看token的第三段 RC-BYCe_UZ2URtWddUpWXIp4NMsoeq2O6UF-8tVplqXY1-CI9u1-a-9DAAJGfNWkHE81mpnR3gXzfrBAB3WUAg
。同樣使用Base64解碼之后,咦,這是什么東東
D X DmYTeȧLUZcPZ0$gZAY_7wY@
最后一段其實是簽名,這個簽名必須知道秘鑰才能計算。這個也是JWT的安全保障。這里提一點注意事項,由于數(shù)據(jù)聲明(Claim)是公開的,千萬不要把密碼等敏感字段放進去,否則就等于是公開給別人了。
也就是說JWT是由三段組成的,按官方的叫法分別是header(頭)、payload(負載)和signature(簽名):
header.payload.signature
頭中的數(shù)據(jù)通常包含兩部分:一個是我們剛剛看到的 alg,這個詞是 algorithm 的縮寫,就是指明算法。另一個可以添加的字段是token的類型(按RFC 7519實現(xiàn)的token機制不只JWT一種),但如果我們采用的是JWT的話,指定這個就多余了。
{ "alg": "HS512", "typ": "JWT" }
payload中可以放置三類數(shù)據(jù):系統(tǒng)保留的、公共的和私有的:
簽名的過程是這樣的:采用header中聲明的算法,接受三個參數(shù):base64編碼的header、base64編碼的payload和秘鑰(secret)進行運算。簽名這一部分如果你愿意的話,可以采用RSASHA256的方式進行公鑰、私鑰對的方式進行,如果安全性要求的高的話。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
JWT的生成和解析
為了簡化我們的工作,這里引入一個比較成熟的JWT類庫,叫 jjwt ( https://github.com/jwtk/jjwt )。這個類庫可以用于Java和Android的JWT token的生成和驗證。
JWT的生成可以使用下面這樣的代碼完成:
String generateToken(Map<String, Object> claims) { return Jwts.builder() .setClaims(claims) .setExpiration(generateExpirationDate()) .signWith(SignatureAlgorithm.HS512, secret) //采用什么算法是可以自己選擇的,不一定非要采用HS512 .compact(); }
數(shù)據(jù)聲明(Claim)其實就是一個Map,比如我們想放入用戶名,可以簡單的創(chuàng)建一個Map然后put進去就可以了。
Map<String, Object> claims = new HashMap<>(); claims.put(CLAIM_KEY_USERNAME, username());
解析也很簡單,利用 jjwt 提供的parser傳入秘鑰,然后就可以解析token了。
Claims getClaimsFromToken(String token) { Claims claims; try { claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } catch (Exception e) { claims = null; } return claims; }
JWT本身沒啥難度,但安全整體是一個比較復(fù)雜的事情,JWT只不過提供了一種基于token的請求驗證機制。但我們的用戶權(quán)限,對于API的權(quán)限劃分、資源的權(quán)限劃分,用戶的驗證等等都不是JWT負責(zé)的。也就是說,請求驗證后,你是否有權(quán)限看對應(yīng)的內(nèi)容是由你的用戶角色決定的。所以我們這里要利用Spring的一個子項目Spring Security來簡化我們的工作。
Spring Security
Spring Security是一個基于Spring的通用安全框架,里面內(nèi)容太多了,本文的主要目的也不是展開講這個框架,而是如何利用Spring Security和JWT一起來完成API保護。所以關(guān)于Spring Secruity的基礎(chǔ)內(nèi)容或展開內(nèi)容,請自行去官網(wǎng)學(xué)習(xí)( http://projects.spring.io/spring-security/ )。
簡單的背景知識
如果你的系統(tǒng)有用戶的概念的話,一般來說,你應(yīng)該有一個用戶表,最簡單的用戶表,應(yīng)該有三列:Id,Username和Password,類似下表這種
而且不是所有用戶都是一種角色,比如網(wǎng)站管理員、供應(yīng)商、財務(wù)等等,這些角色和網(wǎng)站的直接用戶需要的權(quán)限可能是不一樣的。那么我們就需要一個角色表:
當(dāng)然我們還需要一個可以將用戶和角色關(guān)聯(lián)起來建立映射關(guān)系的表。
這是典型的一個關(guān)系型數(shù)據(jù)庫的用戶角色的設(shè)計,由于我們要使用的MongoDB是一個文檔型數(shù)據(jù)庫,所以讓我們重新審視一下這個結(jié)構(gòu)。
這個數(shù)據(jù)結(jié)構(gòu)的優(yōu)點在于它避免了數(shù)據(jù)的冗余,每個表負責(zé)自己的數(shù)據(jù),通過關(guān)聯(lián)表進行關(guān)系的描述,同時也保證的數(shù)據(jù)的完整性:比如當(dāng)你修改角色名稱后,沒有臟數(shù)據(jù)的產(chǎn)生。
但是這種事情在用戶權(quán)限這個領(lǐng)域發(fā)生的頻率到底有多少呢?有多少人每天不停的改的角色名稱?當(dāng)然如果你的業(yè)務(wù)場景確實是需要保證數(shù)據(jù)完整性,你還是應(yīng)該使用關(guān)系型數(shù)據(jù)庫。但如果沒有高頻的對于角色表的改動,其實我們是不需要這樣的一個設(shè)計的。在MongoDB中我們可以將其簡化為
{ _id: <id_generated> username: 'user', password: 'pass', roles: ['USER', 'ADMIN'] }
基于以上考慮,我們重構(gòu)一下 User 類,
@Data public class User { @Id private String id; @Indexed(unique=true, direction= IndexDirection.DESCENDING, dropDups=true) private String username; private String password; private String email; private Date lastPasswordResetDate; private List<String> roles; }
當(dāng)然你可能發(fā)現(xiàn)這個類有點怪,只有一些field,這個簡化的能力是一個叫l(wèi)ombok類庫提供的 ,這個很多開發(fā)過Android的童鞋應(yīng)該熟悉,是用來簡化POJO的創(chuàng)建的一個類庫。簡單說一下,采用 lombok 提供的 @Data 修飾符后可以簡寫成,原來的一坨getter和setter以及constructor等都不需要寫了。類似的 Todo 可以改寫成:
@Data public class Todo { @Id private String id; private String desc; private boolean completed; private User user; }
增加這個類庫只需在 build.gradle 中增加下面這行
dependencies { // 省略其它依賴 compile("org.projectlombok:lombok:${lombokVersion}") }
引入Spring Security
要在Spring Boot中引入Spring Security非常簡單,修改 build.gradle,增加一個引用 org.springframework.boot:spring-boot-starter-security:
dependencies { compile("org.springframework.boot:spring-boot-starter-data-rest") compile("org.springframework.boot:spring-boot-starter-data-mongodb") compile("org.springframework.boot:spring-boot-starter-security") compile("io.jsonwebtoken:jjwt:${jjwtVersion}") compile("org.projectlombok:lombok:${lombokVersion}") testCompile("org.springframework.boot:spring-boot-starter-test") }
你可能發(fā)現(xiàn)了,我們不只增加了對Spring Security的編譯依賴,還增加 jjwt 的依賴。
Spring Security需要我們實現(xiàn)幾個東西,第一個是UserDetails:這個接口中規(guī)定了用戶的幾個必須要有的方法,所以我們創(chuàng)建一個JwtUser類來實現(xiàn)這個接口。為什么不直接使用User類?因為這個UserDetails完全是為了安全服務(wù)的,它和我們的領(lǐng)域類可能有部分屬性重疊,但很多的接口其實是安全定制的,所以最好新建一個類:
public class JwtUser implements UserDetails { private final String id; private final String username; private final String password; private final String email; private final Collection<? extends GrantedAuthority> authorities; private final Date lastPasswordResetDate; public JwtUser( String id, String username, String password, String email, Collection<? extends GrantedAuthority> authorities, Date lastPasswordResetDate) { this.id = id; this.username = username; this.password = password; this.email = email; this.authorities = authorities; this.lastPasswordResetDate = lastPasswordResetDate; } //返回分配給用戶的角色列表 @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @JsonIgnore public String getId() { return id; } @JsonIgnore @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } // 賬戶是否未過期 @JsonIgnore @Override public boolean isAccountNonExpired() { return true; } // 賬戶是否未鎖定 @JsonIgnore @Override public boolean isAccountNonLocked() { return true; } // 密碼是否未過期 @JsonIgnore @Override public boolean isCredentialsNonExpired() { return true; } // 賬戶是否激活 @JsonIgnore @Override public boolean isEnabled() { return true; } // 這個是自定義的,返回上次密碼重置日期 @JsonIgnore public Date getLastPasswordResetDate() { return lastPasswordResetDate; } }
這個接口中規(guī)定的很多方法我們都簡單粗暴的設(shè)成直接返回某個值了,這是為了簡單起見,你在實際開發(fā)環(huán)境中還是要根據(jù)具體業(yè)務(wù)調(diào)整。當(dāng)然由于兩個類還是有一定關(guān)系的,為了寫起來簡單,我們寫一個工廠類來由領(lǐng)域?qū)ο髣?chuàng)建 JwtUser,這個工廠就叫 JwtUserFactory 吧:
public final class JwtUserFactory { private JwtUserFactory() { } public static JwtUser create(User user) { return new JwtUser( user.getId(), user.getUsername(), user.getPassword(), user.getEmail(), mapToGrantedAuthorities(user.getRoles()), user.getLastPasswordResetDate() ); } private static List<GrantedAuthority> mapToGrantedAuthorities(List<String> authorities) { return authorities.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); } }
第二個要實現(xiàn)的是 UserDetailsService,這個接口只定義了一個方法 loadUserByUsername,顧名思義,就是提供一種從用戶名可以查到用戶并返回的方法。注意,不一定是數(shù)據(jù)庫哦,文本文件、xml文件等等都可能成為數(shù)據(jù)源,這也是為什么Spring提供這樣一個接口的原因:保證你可以采用靈活的數(shù)據(jù)源。接下來我們建立一個 JwtUserDetailsServiceImpl 來實現(xiàn)這個接口。
@Service public class JwtUserDetailsServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username)); } else { return JwtUserFactory.create(user); } } }
為了讓Spring可以知道我們想怎樣控制安全性,我們還需要建立一個安全配置類 WebSecurityConfig:
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ // Spring會自動尋找同樣類型的具體類注入,這里就是JwtUserDetailsServiceImpl了 @Autowired private UserDetailsService userDetailsService; @Autowired public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder // 設(shè)置UserDetailsService .userDetailsService(this.userDetailsService) // 使用BCrypt進行密碼的hash .passwordEncoder(passwordEncoder()); } // 裝載BCrypt密碼編碼器 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity // 由于使用的是JWT,我們這里不需要csrf .csrf().disable() // 基于token,所以不需要session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() //.antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 允許對于網(wǎng)站靜態(tài)資源的無授權(quán)訪問 .antMatchers( HttpMethod.GET, "/", "/*.html", "/favicon.ico", "/**/*.html", "/**/*.css", "/**/*.js" ).permitAll() // 對于獲取token的rest api要允許匿名訪問 .antMatchers("/auth/**").permitAll() // 除上面外的所有請求全部需要鑒權(quán)認證 .anyRequest().authenticated(); // 禁用緩存 httpSecurity.headers().cacheControl(); } }
接下來我們要規(guī)定一下哪些資源需要什么樣的角色可以訪問了,在 UserController 加一個修飾符 @PreAuthorize("hasRole('ADMIN')") 表示這個資源只能被擁有 ADMIN 角色的用戶訪問。
/** * 在 @PreAuthorize 中我們可以利用內(nèi)建的 SPEL 表達式:比如 'hasRole()' 來決定哪些用戶有權(quán)訪問。 * 需注意的一點是 hasRole 表達式認為每個角色名字前都有一個前綴 'ROLE_'。所以這里的 'ADMIN' 其實在 * 數(shù)據(jù)庫中存儲的是 'ROLE_ADMIN' 。這個 @PreAuthorize 可以修飾Controller也可修飾Controller中的方法。 **/ @RestController @RequestMapping("/users") @PreAuthorize("hasRole('ADMIN')") public class UserController { @Autowired private UserRepository repository; @RequestMapping(method = RequestMethod.GET) public List<User> getUsers() { return repository.findAll(); } // 略去其它部分 }
類似的我們給 TodoController 加上 @PreAuthorize("hasRole('USER')"),標(biāo)明這個資源只能被擁有 USER 角色的用戶訪問:
@RestController @RequestMapping("/todos") @PreAuthorize("hasRole('USER')") public class TodoController { // 略去 }
使用application.yml配置SpringBoot應(yīng)用
現(xiàn)在應(yīng)該Spring Security可以工作了,但為了可以更清晰的看到工作日志,我們希望配置一下,在和 src 同級建立一個config文件夾,在這個文件夾下面新建一個 application.yml。
# Server configuration server: port: 8090 contextPath: # Spring configuration spring: jackson: serialization: INDENT_OUTPUT: true data.mongodb: host: localhost port: 27017 database: springboot # Logging configuration logging: level: org.springframework: data: DEBUG security: DEBUG
我們除了配置了logging的一些東東外,也順手設(shè)置了數(shù)據(jù)庫和http服務(wù)的一些配置項,現(xiàn)在我們的服務(wù)器會在8090端口監(jiān)聽,而spring data和security的日志在debug模式下會輸出到console。
現(xiàn)在啟動服務(wù)后,訪問 http://localhost:8090 你可以看到根目錄還是正常顯示的
根目錄還是正??梢栽L問的
但我們試一下 http://localhost:8090/users ,觀察一下console,我們會看到如下的輸出,告訴由于用戶未鑒權(quán),我們訪問被拒絕了。
2017-03-10 15:51:53.351 DEBUG 57599 --- [nio-8090-exec-4] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point org.springframework.security.access.AccessDeniedException: Access is denied at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-4.2.1.RELEASE.jar:4.2.1.RELEASE]
集成JWT和Spring Security
到現(xiàn)在,我們還是讓JWT和Spring Security各自為戰(zhàn),并沒有集成起來。要想要JWT在Spring中工作,我們應(yīng)該新建一個filter,并把它配置在 WebSecurityConfig 中。
@Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Value("${jwt.header}") private String tokenHeader; @Value("${jwt.tokenHead}") private String tokenHead; @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String authHeader = request.getHeader(this.tokenHeader); if (authHeader != null && authHeader.startsWith(tokenHead)) { final String authToken = authHeader.substring(tokenHead.length()); // The part after "Bearer " String username = jwtTokenUtil.getUsernameFromToken(authToken); logger.info("checking authentication " + username); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails( request)); logger.info("authenticated user " + username + ", setting security context"); SecurityContextHolder.getContext().setAuthentication(authentication); } } } chain.doFilter(request, response); } }
事實上如果我們足夠相信token中的數(shù)據(jù),也就是我們足夠相信簽名token的secret的機制足夠好,這種情況下,我們可以不用再查詢數(shù)據(jù)庫,而直接采用token中的數(shù)據(jù)。本例中,我們還是通過Spring Security的 @UserDetailsService 進行了數(shù)據(jù)查詢,但簡單驗證的話,你可以采用直接驗證token是否合法來避免昂貴的數(shù)據(jù)查詢。
接下來,我們會在 WebSecurityConfig 中注入這個filter,并且配置到 HttpSecurity 中:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ // 省略其它部分 @Bean public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception { return new JwtAuthenticationTokenFilter(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { // 省略之前寫的規(guī)則部分,具體看前面的代碼 // 添加JWT filter httpSecurity .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); } }
完成鑒權(quán)(登錄)、注冊和更新token的功能
到現(xiàn)在,我們整個API其實已經(jīng)在安全的保護下了,但我們遇到一個問題:所有的API都安全了,但我們還沒有用戶啊,所以所有API都沒法訪問。因此要提供一個注冊、登錄的API,這個API應(yīng)該是可以匿名訪問的。給它規(guī)劃的路徑呢,我們前面其實在WebSecurityConfig中已經(jīng)給出了,就是 /auth。
首先需要一個AuthService,規(guī)定一下必選動作:
public interface AuthService { User register(User userToAdd); String login(String username, String password); String refresh(String oldToken); }
然后,實現(xiàn)這些必選動作,其實非常簡單:
@Service public class AuthServiceImpl implements AuthService { private AuthenticationManager authenticationManager; private UserDetailsService userDetailsService; private JwtTokenUtil jwtTokenUtil; private UserRepository userRepository; @Value("${jwt.tokenHead}") private String tokenHead; @Autowired public AuthServiceImpl( AuthenticationManager authenticationManager, UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, UserRepository userRepository) { this.authenticationManager = authenticationManager; this.userDetailsService = userDetailsService; this.jwtTokenUtil = jwtTokenUtil; this.userRepository = userRepository; } @Override public User register(User userToAdd) { final String username = userToAdd.getUsername(); if(userRepository.findByUsername(username)!=null) { return null; } BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); final String rawPassword = userToAdd.getPassword(); userToAdd.setPassword(encoder.encode(rawPassword)); userToAdd.setLastPasswordResetDate(new Date()); userToAdd.setRoles(asList("ROLE_USER")); return userRepository.insert(userToAdd); } @Override public String login(String username, String password) { UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password); final Authentication authentication = authenticationManager.authenticate(upToken); SecurityContextHolder.getContext().setAuthentication(authentication); final UserDetails userDetails = userDetailsService.loadUserByUsername(username); final String token = jwtTokenUtil.generateToken(userDetails); return token; } @Override public String refresh(String oldToken) { final String token = oldToken.substring(tokenHead.length()); String username = jwtTokenUtil.getUsernameFromToken(token); JwtUser user = (JwtUser) userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.canTokenBeRefreshed(token, user.getLastPasswordResetDate())){ return jwtTokenUtil.refreshToken(token); } return null; } }
然后建立AuthController就好,這個AuthController中我們在其中使用了表達式綁定,比如 @Value("${jwt.header}")中的 jwt.header 其實是定義在 applicaiton.yml 中的
# JWT jwt: header: Authorization secret: mySecret expiration: 604800 tokenHead: "Bearer " route: authentication: path: auth refresh: refresh register: "auth/register"
同樣的 @RequestMapping(value = "${jwt.route.authentication.path}", method = RequestMethod.POST) 中的 jwt.route.authentication.path 也是定義在上面的
@RestController public class AuthController { @Value("${jwt.header}") private String tokenHeader; @Autowired private AuthService authService; @RequestMapping(value = "${jwt.route.authentication.path}", method = RequestMethod.POST) public ResponseEntity<?> createAuthenticationToken( @RequestBody JwtAuthenticationRequest authenticationRequest) throws AuthenticationException{ final String token = authService.login(authenticationRequest.getUsername(), authenticationRequest.getPassword()); // Return the token return ResponseEntity.ok(new JwtAuthenticationResponse(token)); } @RequestMapping(value = "${jwt.route.authentication.refresh}", method = RequestMethod.GET) public ResponseEntity<?> refreshAndGetAuthenticationToken( HttpServletRequest request) throws AuthenticationException{ String token = request.getHeader(tokenHeader); String refreshedToken = authService.refresh(token); if(refreshedToken == null) { return ResponseEntity.badRequest().body(null); } else { return ResponseEntity.ok(new JwtAuthenticationResponse(refreshedToken)); } } @RequestMapping(value = "${jwt.route.authentication.register}", method = RequestMethod.POST) public User register(@RequestBody User addedUser) throws AuthenticationException{ return authService.register(addedUser); } }
驗證時間
接下來,我們就可以看看我們的成果了,首先注冊一個用戶 peng2,很完美的注冊成功了
注冊用戶
然后在 /auth 中取得token,也很成功
取得token
不使用token時,訪問 /users 的結(jié)果,不出意料的失敗,提示未授權(quán)。
不使用token訪問users列表
使用token時,訪問 /users 的結(jié)果,雖然仍是失敗,但這次提示訪問被拒絕,意思就是雖然你已經(jīng)得到了授權(quán),但由于你的會員級別還只是普卡會員,所以你的請求被拒絕。
image_1bas22va52vk1rj445fhm87k72a.png-156.9kB
接下來我們訪問 /users/?username=peng2,竟然可以訪問啊
訪問自己的信息是允許的
這是由于我們?yōu)檫@個方法定義的權(quán)限就是:擁有ADMIN角色或者是當(dāng)前用戶本身。Spring Security真是很方便,很強大。
@PostAuthorize("returnObject.username == principal.username or hasRole('ROLE_ADMIN')") @RequestMapping(value = "/",method = RequestMethod.GET) public User getUserByUsername(@RequestParam(value="username") String username) { return repository.findByUsername(username); }
本章代碼: https://github.com/wpcfan/spring-boot-tut/tree/chap04
以上所述是小編給大家介紹的Spring Boot(四)之使用JWT和Spring Security保護REST API,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對億速云網(wǎng)站的支持!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。