使用JWT和Spring Security保護REST API,重拾後端之Spring Boot

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

用戶名和密碼鑑權,使用Session保存用戶鑑權結果。 使用OAuth進行鑑權(其實OAuth也是一種基於Token的鑑權,只是沒有規定Token的生成方式) 自行採用Token進行鑑權 第一種就不介紹了,因爲依賴Session來維護狀態,也不太適合移動時代,新的項目就不要採用了。第二種OAuth的方案和JWT都是基於Token的,但OAuth其實對於不作開放平臺的公司有些過於複雜。咱們主要介紹第三種:JWT。html

什麼是JWT?java

JWT是 Json Web Token 的縮寫。它是基於 RFC 7519 標準定義的一種能夠安全傳輸的 小巧 和 自包含 的JSON對象。因爲數據是使用數字簽名的,因此是可信任的和安全的。JWT可使用HMAC算法對secret進行加密或者使用RSA的公鑰私鑰對來進行簽名。git

JWT的工做流程github

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

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

JWT工做流程圖spring

爲了更好的理解這個token是什麼,咱們先來看一個token生成後的樣子,下面那坨亂糟糟的就是了。mongodb

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ3YW5nIiwiY3JlYXRlZCI6MTQ4OTA3OTk4MTM5MywiZXhwIjoxNDg5Njg0NzgxfQ.RC-BYCe_UZ2URtWddUpWXIp4NMsoeq2O6UF-8tVplqXY1-CI9u1-a-9DAAJGfNWkHE81mpnR3gXzfrBAB3WUAg 但仔細看到的話仍是能夠看到這個token分紅了三部分,每部分用 . 分隔,每段都是用 Base64 編碼的。若是咱們用一個Base64的解碼器的話 ( www.base64decode.org/ ),能夠看到第一部分 eyJhbGciOiJIUzUxMiJ9 被解析成了:數據庫

{

"alg":"HS512"

}

這是告訴咱們HMAC採用HS512算法對JWT進行的簽名。

第二部分 eyJzdWIiOiJ3YW5nIiwiY3JlYXRlZCI6MTQ4OTA3OTk4MTM5MywiZXhwIjoxNDg5Njg0NzgxfQ 被解碼以後是

{ "sub":"wang", "created":1489079981393, "exp":1489684781 } 這段告訴咱們這個Token中含有的數據聲明(Claim),這個例子裏面有三個聲明:sub, created 和 exp。在咱們這個例子中,分別表明着用戶名、建立時間和過時時間,固然你能夠把任意數據聲明在這裏。

看到這裏,你可能會想這是個什麼鬼token,全部信息都透明啊,安全怎麼保障?別急,咱們看看token的第三段 RC-BYCe_UZ2URtWddUpWXIp4NMsoeq2O6UF-8tVplqXY1-CI9u1-a-9DAAJGfNWkHE81mpnR3gXzfrBAB3WUAg。一樣使用Base64解碼以後,咦,這是什麼東東

D X �DmYTeȧL�UZcPZ0$gZAY�_7�wY@ 最後一段實際上是簽名,這個簽名必須知道祕鑰才能計算。這個也是JWT的安全保障。這裏提一點注意事項,因爲數據聲明(Claim)是公開的,千萬不要把密碼等敏感字段放進去,不然就等因而公開給別人了。

也就是說JWT是由三段組成的,按官方的叫法分別是header(頭)、payload(負載)和signature(簽名):

header.payload.signature 頭中的數據一般包含兩部分:一個是咱們剛剛看到的 alg,這個詞是 algorithm 的縮寫,就是指明算法。另外一個能夠添加的字段是token的類型(按RFC 7519實現的token機制不僅JWT一種),但若是咱們採用的是JWT的話,指定這個就多餘了。

{ "alg": "HS512", "typ": "JWT" } payload中能夠放置三類數據:系統保留的、公共的和私有的:

系統保留的聲明(Reserved claims):這類聲明不是必須的,可是是建議使用的,包括:iss (簽發者), exp (過時時間), sub (主題), aud (目標受衆)等。這裏咱們發現都用的縮寫的三個字符,這是因爲JWT的目標就是儘量小巧。 公共聲明:這類聲明須要在 IANA JSON Web Token Registry 中定義或者提供一個URI,由於要避免重名等衝突。 私有聲明:這個就是你根據業務須要本身定義的數據了。 簽名的過程是這樣的:採用header中聲明的算法,接受三個參數:base64編碼的header、base64編碼的payload和祕鑰(secret)進行運算。簽名這一部分若是你願意的話,能夠採用RSASHA256的方式進行公鑰、私鑰對的方式進行,若是安全性要求的高的話。

HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret) JWT的生成和解析

爲了簡化咱們的工做,這裏引入一個比較成熟的JWT類庫,叫 jjwt ( 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(); } 數據聲明(Claim)其實就是一個Map,好比咱們想放入用戶名,能夠簡單的建立一個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自己沒啥難度,但安全總體是一個比較複雜的事情,JWT只不過提供了一種基於token的請求驗證機制。但咱們的用戶權限,對於API的權限劃分、資源的權限劃分,用戶的驗證等等都不是JWT負責的。也就是說,請求驗證後,你是否有權限看對應的內容是由你的用戶角色決定的。因此咱們這裏要利用Spring的一個子項目Spring Security來簡化咱們的工做。

Spring Security

Spring Security是一個基於Spring的通用安全框架,裏面內容太多了,本文的主要目的也不是展開講這個框架,而是如何利用Spring Security和JWT一塊兒來完成API保護。因此關於Spring Secruity的基礎內容或展開內容,請自行去官網學習( projects.spring.io/spring-secu… )。

簡單的背景知識

若是你的系統有用戶的概念的話,通常來講,你應該有一個用戶表,最簡單的用戶表,應該有三列:Id,Username和Password,相似下表這種

ID USERNAME PASSWORD 10 wang abcdefg 並且不是全部用戶都是一種角色,好比網站管理員、供應商、財務等等,這些角色和網站的直接用戶須要的權限多是不同的。那麼咱們就須要一個角色表:

ID ROLE 10 USER 20 ADMIN 固然咱們還須要一個能夠將用戶和角色關聯起來創建映射關係的表。

USER_ID ROLE_ID 10 10 20 20 這是典型的一個關係型數據庫的用戶角色的設計,因爲咱們要使用的MongoDB是一個文檔型數據庫,因此讓咱們從新審視一下這個結構。

這個數據結構的優勢在於它避免了數據的冗餘,每一個表負責本身的數據,經過關聯表進行關係的描述,同時也保證的數據的完整性:好比當你修改角色名稱後,沒有髒數據的產生。

可是這種事情在用戶權限這個領域發生的頻率到底有多少呢?有多少人天天不停的改的角色名稱?固然若是你的業務場景確實是須要保證數據完整性,你仍是應該使用關係型數據庫。但若是沒有高頻的對於角色表的改動,其實咱們是不須要這樣的一個設計的。在MongoDB中咱們能夠將其簡化爲

{ _id: <id_generated> username: 'user', password: 'pass', roles: ['USER', 'ADMIN'] } 基於以上考慮,咱們重構一下 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 roles; } 固然你可能發現這個類有點怪,只有一些field,這個簡化的能力是一個叫lombok類庫提供的 ,這個不少開發過Android的童鞋應該熟悉,是用來簡化POJO的建立的一個類庫。簡單說一下,採用 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") } 你可能發現了,咱們不僅增長了對Spring Security的編譯依賴,還增長 jjwt 的依賴。

Spring Security須要咱們實現幾個東西,第一個是UserDetails:這個接口中規定了用戶的幾個必需要有的方法,因此咱們建立一個JwtUser類來實現這個接口。爲何不直接使用User類?由於這個UserDetails徹底是爲了安全服務的,它和咱們的領域類可能有部分屬性重疊,但不少的接口實際上是安全定製的,因此最好新建一個類:

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; } } 這個接口中規定的不少方法咱們都簡單粗暴的設成直接返回某個值了,這是爲了簡單起見,你在實際開發環境中仍是要根據具體業務調整。固然因爲兩個類仍是有必定關係的,爲了寫起來簡單,咱們寫一個工廠類來由領域對象建立 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 mapToGrantedAuthorities(List authorities) { return authorities.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); } } 第二個要實現的是 UserDetailsService,這個接口只定義了一個方法 loadUserByUsername,顧名思義,就是提供一種從用戶名能夠查到用戶並返回的方法。注意,不必定是數據庫哦,文本文件、xml文件等等均可能成爲數據源,這也是爲何Spring提供這樣一個接口的緣由:保證你能夠採用靈活的數據源。接下來咱們創建一個 JwtUserDetailsServiceImpl 來實現這個接口。

@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 // 設置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() // 容許對於網站靜態資源的無受權訪問 .antMatchers( HttpMethod.GET, "/", "/*.html", "/favicon.ico", "//.html", "/**/.css", "//*.js" ).permitAll() // 對於獲取token的rest api要容許匿名訪問 .antMatchers("/auth/").permitAll() // 除上面外的全部請求所有須要鑑權認證 .anyRequest().authenticated(); // 禁用緩存 httpSecurity.headers().cacheControl(); } } 接下來咱們要規定一下哪些資源須要什麼樣的角色能夠訪問了,在 UserController 加一個修飾符 @PreAuthorize("hasRole('ADMIN')") 表示這個資源只能被擁有 ADMIN 角色的用戶訪問。

/**

  • 在 @PreAuthorize 中咱們能夠利用內建的 SPEL 表達式:好比 'hasRole()' 來決定哪些用戶有權訪問。
  • 需注意的一點是 hasRole 表達式認爲每一個角色名字前都有一個前綴 'ROLE_'。因此這裏的 'ADMIN' 其實在
  • 數據庫中存儲的是 'ROLE_ADMIN' 。這個 @PreAuthorize 能夠修飾Controller也可修飾Controller中的方法。 **/ @RestController @RequestMapping("/users") @PreAuthorize("hasRole('ADMIN')") public class UserController { @Autowired private UserRepository repository; @RequestMapping(method = RequestMethod.GET) public List getUsers() { return repository.findAll(); } // 略去其它部分 } 相似的咱們給 TodoController 加上 @PreAuthorize("hasRole('USER')"),標明這個資源只能被擁有 USER 角色的用戶訪問:

@RestController @RequestMapping("/todos") @PreAuthorize("hasRole('USER')") public class TodoController { // 略去 } 使用application.yml配置SpringBoot應用

如今應該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的一些東東外,也順手設置了數據庫和http服務的一些配置項,如今咱們的服務器會在8090端口監聽,而spring data和security的日誌在debug模式下會輸出到console。

如今啓動服務後,訪問 http://localhost:8090 你能夠看到根目錄仍是正常顯示的

根目錄仍是正常能夠訪問的

但咱們試一下 http://localhost:8090/users ,觀察一下console,咱們會看到以下的輸出,告訴因爲用戶未鑑權,咱們訪問被拒絕了。

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

到如今,咱們仍是讓JWT和Spring Security各自爲戰,並無集成起來。要想要JWT在Spring中工做,咱們應該新建一個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中的數據,也就是咱們足夠相信簽名token的secret的機制足夠好,這種狀況下,咱們能夠不用再查詢數據庫,而直接採用token中的數據。本例中,咱們仍是經過Spring Security的 @UserDetailsService 進行了數據查詢,但簡單驗證的話,你能夠採用直接驗證token是否合法來避免昂貴的數據查詢。

接下來,咱們會在 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 { // 省略以前寫的規則部分,具體看前面的代碼 // 添加JWT filter httpSecurity .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); } } 完成鑑權(登陸)、註冊和更新token的功能

到如今,咱們整個API其實已經在安全的保護下了,但咱們遇到一個問題:全部的API都安全了,但咱們尚未用戶啊,因此全部API都無法訪問。所以要提供一個註冊、登陸的API,這個API應該是能夠匿名訪問的。給它規劃的路徑呢,咱們前面其實在WebSecurityConfig中已經給出了,就是 /auth。

首先須要一個AuthService,規定一下必選動做:

public interface AuthService { User register(User userToAdd); String login(String username, String password); String refresh(String oldToken); } 而後,實現這些必選動做,其實很是簡單:

登陸時要生成token,完成Spring Security認證,而後返回token給客戶端 註冊時將用戶密碼用BCrypt加密,寫入用戶角色,因爲是開放註冊,因此寫入角色系統控制,將其寫成 ROLE_USER 提供一個能夠刷新token的接口 refresh 用於取得新的token @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 的結果,不出意料的失敗,提示未受權。

不使用token訪問users列表

使用token時,訪問 /users 的結果,雖然還是失敗,但此次提示訪問被拒絕,意思就是雖然你已經獲得了受權,但因爲你的會員級別還只是普卡會員,因此你的請求被拒絕。

image_1bas22va52vk1rj445fhm87k72a.png-156.9kB

接下來咱們訪問 /users/?username=peng2,居然能夠訪問啊

訪問本身的信息是容許的

這是因爲咱們爲這個方法定義的權限就是:擁有ADMIN角色或者是當前用戶自己。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); } 歡的點點關注,點點贊。 對Java技術,架構技術感興趣的同窗,歡迎加QQ羣 一塊兒學習,相互討論。私信我便可入羣交流

羣內已經有小夥伴將知識體系整理好(源碼,筆記,PPT,學習視頻),歡迎加羣領取

相關文章
相關標籤/搜索