回顧前一天講的MVC,下達request到Controller後,由Service去執行資料的 增/刪/改/查,最後再呈現在View上
在以前,我們會新增一支class繼承 HttpServlet,然後實作doGet、doPost,
除了要寫一堆if判斷request並做出對應的操作、還要負責頁面的轉導等等工作
現在有了spring boot,而且做的是前後端分離,
只要在Controller做出能讓前端好call的api就行了,做起來也相對簡單明瞭
今天就來做User的登入api
這邊我想仿作銀行的三層式帳密(ID+User+Password),
在這之前,我有在Service再補加一個verifyUser
的方法,是用來驗證登入帳號密碼的,
其實就只是簡單的檢查字串,
實際上銀行的帳號密碼驗證是不會這麼陽春的,
而且傳入Server的值會在client端就先加密,
確保封包不會被側錄(end-to-end encryption),
總之很安全,有空的話就再來試著實作簡單版的ETOEE
public String verifyUser(String email,String userAcct , String userPasswd){
/*
0000 login success
0001 wrong email
0002 wrong useracct
0003 wrong passwd
0004 acct locked
9999 unknown error
*/
Optional<User> user=userRepo.findUserByEmail(email);
String result="9999";
if(user.isPresent()){
if(user.get().getUserAccount().equals(userAcct)){
if(user.get().getUserPassword().equals(userPasswd)){
result="0000";
}else{
result="0003";
}
}else{
result="0002";
}
}else{
result="0001";
}
return result;
}
本來想再加個密碼錯5次就鎖定帳號跟檢查帳號狀態,之後有時間再補充好了
接著來到controller,今天要實作登入的api
@RestController
@RequestMapping("api/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping("/login")
public ResponseEntity<String> userLogin(@RequestBody Map<String,String> map){
String email= map.get("email");
String userAccount= map.get("userAccount");
String userPassword= map.get("userPassword");
String result="null data";
if(StringUtils.isBlank(email)||StringUtils.isBlank(userAccount)||StringUtils.isBlank(userPassword)){
return ResponseEntity.ok(result);
}else{
result=userService.verifyUser(email, userAccount, userPassword);
return ResponseEntity.ok(result);
}
}
}
在這邊我用了5個annotation:
@RestController
用來告訴Spring這是一個RestController,
@RestController
跟@Controller
的區別是@RestController
下的方法return的值會直接被預設為Response body回傳@RestController
也等於是@Controller
+@ResponseBody
@RestController
適用於api這樣直接回傳值的需求@Controller
則是適合用在jsp、thymeleaf這種Server同時也負責頁面渲染的情況
接著,當Spring知道這是一個Controller後就會去檢查有沒有@RequestMapping
並將這個Controller綁在相對的url上,像我設定的是"api/user",
因此會對應到的url是 localhost:8080/api/user
@RequiredArgsConstructor
跟昨天在Service的用法一樣是用來實體化物件用的就不贅述
@PostMapping
應該很好理解
就是對應瀏覽器的HTTP method的實作
相關的有:@GetMapping
@PostMapping
@DeleteMapping
@PutMapping
@PatchMapping
我在這邊決定以POST方法來實做登入功能,
@PostMapping("/login")對應到 localhost:8080/api/user/login
@PostMapping("/login")
public ResponseEntity<String> userLogin(@RequestBody HashMap<String,String> map){
String email= map.get("email");
String userAccount= map.get("userAccount");
String userPassword= map.get("userPassword");
String result="請輸入帳號密碼!";
if(StringUtils.isBlank(email)||StringUtils.isBlank(userAccount)||StringUtils.isBlank(userPassword)){
return ResponseEntity.ok(result);
}else{
result=userService.verifyUser(email, userAccount, userPassword);
return ResponseEntity.ok(result);
}
}
@RequestBody
會接收Post發送過來的request Body,我選擇以HashMap來儲存,
(其實應該要用User這個Object來存較好,
用HashMap的話除了無法對傳入的值做最低限度的限制,也降低程式碼的可讀性)
Spring會將requestBody轉為Map的形式傳入
雖然到時候在前端會檢查,不過我在這邊還是有做一下null檢核,
org.apache.commons.lang3.StringUtils,好用!
要記得在pom.xml加個dependency
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
本來在想登入成功跟失敗的Response Code是不是要有區別比較好,不過還沒想到前端到時候會怎麼做
就決定先全部都回200,也就是ok
在測試api時發現post方法一直回傳"status":403,"error":"Forbidden"
查了一下發現原來是因為Spring Security的預設設定有CSRF的保護
會阻擋沒帶CSRF token的POST、PATCH、PUT、DELETE等http method
為求測試方便,先把這個功能關閉,順便也關掉Spring Security的登入頁
建立一個WebSecurityConfig.java,
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll() //讓所有頁面都不用登入
.and().csrf().disable(); //關掉csrf保護
}
}
接著再測就成功把post送到server了
回0000,表示帳號密碼正確
今天就先這樣吧!