重拾後端之Spring Boot(一):REST API的搭建能夠這樣簡單
重拾後端之Spring Boot(二):MongoDb的無縫集成
重拾後端之Spring Boot(三):找回熟悉的Controller,Service
重拾後端之Spring Boot(四):使用 JWT 和 Spring Security 保護 REST APIjavascript
一般狀況下,把API直接暴露出去是風險很大的,不說別的,直接被機器攻擊就喝一壺的。那麼通常來講,對API要劃分出必定的權限級別,而後作一個用戶的鑑權,依據鑑權結果給予用戶開放對應的API。目前,比較主流的方案有幾種:css
第一種就不介紹了,因爲依賴Session來維護狀態,也不太適合移動時代,新的項目就不要採用了。第二種OAuth的方案和JWT都是基於Token的,但OAuth其實對於不作開放平臺的公司有些過於複雜。咱們主要介紹第三種:JWT。html
JWT是 Json Web Token
的縮寫。它是基於 RFC 7519 標準定義的一種能夠安全傳輸的 小巧 和 自包含 的JSON對象。因爲數據是使用數字簽名的,因此是可信任的和安全的。JWT可使用HMAC算法對secret進行加密或者使用RSA的公鑰私鑰對來進行簽名。java
下面是一個JWT的工做流程圖。模擬一下實際的流程是這樣的(假設受保護的API在/protected
中)git
/protected
中的API時,在請求的header中加入 Authorization: Bearer xxxx(token)
。此處注意token以前有一個7字符長度的 Bearer
爲了更好的理解這個token是什麼,咱們先來看一個token生成後的樣子,下面那坨亂糟糟的就是了。github
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ3YW5nIiwiY3JlYXRlZCI6MTQ4OTA3OTk4MTM5MywiZXhwIjoxNDg5Njg0NzgxfQ.RC-BYCe_UZ2URtWddUpWXIp4NMsoeq2O6UF-8tVplqXY1-CI9u1-a-9DAAJGfNWkHE81mpnR3gXzfrBAB3WUAg複製代碼
但仔細看到的話仍是能夠看到這個token分紅了三部分,每部分用 .
分隔,每段都是用 Base64 編碼的。若是咱們用一個Base64的解碼器的話 ( www.base64decode.org/ ),能夠看到第一部分 eyJhbGciOiJIUzUxMiJ9
被解析成了: web
{
"alg":"HS512"
}複製代碼
這是告訴咱們HMAC採用HS512算法對JWT進行的簽名。算法
第二部分 eyJzdWIiOiJ3YW5nIiwiY3JlYXRlZCI6MTQ4OTA3OTk4MTM5MywiZXhwIjoxNDg5Njg0NzgxfQ
被解碼以後是 spring
{
"sub":"wang",
"created":1489079981393,
"exp":1489684781
}複製代碼
這段告訴咱們這個Token中含有的數據聲明(Claim),這個例子裏面有三個聲明:sub
, created
和 exp
。在咱們這個例子中,分別表明着用戶名、建立時間和過時時間,固然你能夠把任意數據聲明在這裏。mongodb
看到這裏,你可能會想這是個什麼鬼token,全部信息都透明啊,安全怎麼保障?別急,咱們看看token的第三段 RC-BYCe_UZ2URtWddUpWXIp4NMsoeq2O6UF-8tVplqXY1-CI9u1-a-9DAAJGfNWkHE81mpnR3gXzfrBAB3WUAg
。一樣使用Base64解碼以後,咦,這是什麼東東
D X DmYTeȧLUZcPZ0$gZAY_7wY@複製代碼
最後一段實際上是簽名,這個簽名必須知道祕鑰才能計算。這個也是JWT的安全保障。這裏提一點注意事項,因爲數據聲明(Claim)是公開的,千萬不要把密碼等敏感字段放進去,不然就等因而公開給別人了。
也就是說JWT是由三段組成的,按官方的叫法分別是header(頭)、payload(負載)和signature(簽名):
header.payload.signature複製代碼
頭中的數據一般包含兩部分:一個是咱們剛剛看到的 alg
,這個詞是 algorithm
的縮寫,就是指明算法。另外一個能夠添加的字段是token的類型(按RFC 7519實現的token機制不僅JWT一種),但若是咱們採用的是JWT的話,指定這個就多餘了。
{
"alg": "HS512",
"typ": "JWT"
}複製代碼
payload中能夠放置三類數據:系統保留的、公共的和私有的:
簽名的過程是這樣的:採用header中聲明的算法,接受三個參數:base64編碼的header、base64編碼的payload和祕鑰(secret)進行運算。簽名這一部分若是你願意的話,能夠採用RSASHA256的方式進行公鑰、私鑰對的方式進行,若是安全性要求的高的話。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)複製代碼
爲了簡化咱們的工做,這裏引入一個比較成熟的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的通用安全框架,裏面內容太多了,本文的主要目的也不是展開講這個框架,而是如何利用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<String> 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 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<GrantedAuthority> mapToGrantedAuthorities(List<String> 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<User> getUsers() {
return repository.findAll();
}
// 略去其它部分
}複製代碼
相似的咱們給 TodoController
加上 @PreAuthorize("hasRole('USER')")
,標明這個資源只能被擁有 USER
角色的用戶訪問:
@RestController
@RequestMapping("/todos")
@PreAuthorize("hasRole('USER')")
public class TodoController {
// 略去
}複製代碼
如今應該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中工做,咱們應該新建一個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);
}
}複製代碼
到如今,咱們整個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);
}複製代碼
而後,實現這些必選動做,其實很是簡單:
ROLE_USER
@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時,訪問 /users
的結果,不出意料的失敗,提示未受權。
使用token時,訪問 /users
的結果,雖然還是失敗,但此次提示訪問被拒絕,意思就是雖然你已經獲得了受權,但因爲你的會員級別還只是普卡會員,因此你的請求被拒絕。
接下來咱們訪問 /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);
}複製代碼
本章代碼: github.com/wpcfan/spri…
重拾後端之Spring Boot(一):REST API的搭建能夠這樣簡單
重拾後端之Spring Boot(二):MongoDb的無縫集成
重拾後端之Spring Boot(三):找回熟悉的Controller,Service
重拾後端之Spring Boot(四):使用 JWT 和 Spring Security 保護 REST API