前言
在 Spring Boot + Vue 前后端分離項目中,后端只提供接口,頁面處理和跳轉都由前端實現,前后端通過 json 傳輸數據。
后端項目,搭建骨架,可以參考文章:使用 MybatisGenerator 根據數據庫自動生成 model、mapper 接口和 mapper.xml
接下來開始后端登錄接口的實現。
處理 User 用戶類
讓 User 類實現接口 UserDetails,並重寫其中的方法:
public class User implements Serializable, UserDetails {
......
@Override
public boolean isAccountNonExpired() {
return true; // 賬戶沒有過期
}
@Override
public boolean isAccountNonLocked() {
return true; // 賬戶沒有鎖定
}
@Override
public boolean isCredentialsNonExpired() {
return true; // 密碼沒有過期
}
@Override
public boolean isEnabled() {
return enabled; // 賬戶可以使用(需要刪除 User 類自帶的 getEnabled 方法,如果有的話)
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null; // 暫不設置
}
......
}
這里的 User 類,就是整個項目存放用戶信息的類,有用戶名、密碼之類的。
UserService 配置
在包 service 下,新建一個 UserService 類,實現接口 UserDetailsService:
@Service
public class UserService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(username);
if (user == null){
throw new UsernameNotFoundException("用戶名不存在!");
}
return user;
}
}
關鍵代碼:
User user = userMapper.loadUserByUsername(username);
這里重寫了 loadUserByUsername 方法,通過用戶名加載用戶信息。
UserMapper 配置
在接口 UserMapper 中,定義 loadUserByUsername 方法:
public interface UserMapper {
......
User loadUserByUsername(String username);
}
UserMapper.xml 配置
在 UserMapper.xml 實現方法 loadUserByUsername ,只需要添加一個查詢語句:
......
<select id="loadUserByUsername" resultMap="BaseResultMap">
select * from user where username=#{username};
</select>
......
編寫 RespBean 類
RespBean 類主要用於向前端返回數據,會在后端很多地方用到,比如 SpringSecurity 配置中就會用到:
public class RespBean {
private Integer status;
private String msg;
private Object obj;
public static RespBean ok(String msg){
return new RespBean(200, msg, null);
}
public static RespBean ok(String msg, Object obj){
return new RespBean(200, msg, obj);
}
public static RespBean error(String msg){
return new RespBean(500, msg, null);
}
public static RespBean error(String msg, Object obj){
return new RespBean(500, msg, obj);
}
private RespBean() {
}
private RespBean(Integer status, String msg, Object obj) {
this.status = status;
this.msg = msg;
this.obj = obj;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
}
注意一下,RespBean 類有兩個構造函數,都是 private 修飾,表明其他的類就不能直接調用 RespBean 生成新的對象,這樣,RespBean 類只有一個對象實例。
這里定義了兩個靜態方法 ok 和 error ,返回值都是 RespBean 對象。
Spring Security 配置
一般在 Spring Boot + Vue 前后端分離項目中,都是采用 Spring Security 作訪問和權限控制。
關於 Spring Security 的權限控制暫且不談,這里主要是登錄接口的配置。
新建一個包 config,再建一個配置類 SecurityConfig ,繼承 WebSecurityConfigurerAdapter :
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserService userService;
// 如果密碼采用 BCryptPasswordEncoder 加密,則取消注釋
// @Bean
// PasswordEncoder passwordEncoder(){
// return new BCryptPasswordEncoder();
// }
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/doLogin")
.loginPage("/login")
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
User user = (User) authentication.getPrincipal();
user.setPassword(null);
RespBean ok = RespBean.ok("登錄成功!", user);
String string = new ObjectMapper().writeValueAsString(ok);
writer.write(string);
writer.flush();
writer.close();
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
RespBean respBean = RespBean.error("登錄失敗!");
if (httpServletResponse instanceof LockedException){
respBean.setMsg("賬戶被鎖定,請聯系管理員!");
}else if (httpServletResponse instanceof BadCredentialsException){
respBean.setMsg("用戶名或密碼輸入錯誤!");
}else if (httpServletResponse instanceof DisabledException){
respBean.setMsg("賬戶被禁用,請聯系管理員!");
}else if (httpServletResponse instanceof AccountExpiredException){
respBean.setMsg("賬戶過期,請聯系管理員!");
}else if (httpServletResponse instanceof CredentialsExpiredException){
respBean.setMsg("密碼過期,請聯系管理員!");
}else {
respBean.setMsg("登錄失敗!");
}
String string = new ObjectMapper().writeValueAsString(respBean);
writer.write(string);
writer.flush();
writer.close();
}
})
.permitAll()
.and()
.logout()
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
writer.write(new ObjectMapper().writeValueAsString(RespBean.ok("注銷登錄")));
writer.flush();
writer.close();
}
})
.permitAll()
.and()
.csrf().disable();
}
}
這個配置類比較長,我們挨個分析。
由於我這個數據庫中 User 類的密碼采用的是 md5DigestAsHex 加密,所以沒有用 SpringSecurity 自帶的加密方式 BCryptPasswordEncoder。
1、方法 configure(AuthenticationManagerBuilder auth) 解釋
代碼如下:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
這個方法身份驗證,將登錄的用戶信息放在 auth 中,通過 userDetailsService 方法設置,傳入的參數就是之前配置好的 userService。
2、方法 configure(HttpSecurity http) 解釋
這個方法用於處理登錄表單,主要分為三大塊:
-
登錄成功的配置
-
登錄失敗的配置
-
注銷登錄的配置
2.1 登錄成功的配置
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/doLogin")
.loginPage("/login")
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
User user = (User) authentication.getPrincipal();
user.setPassword(null);
RespBean ok = RespBean.ok("登錄成功!", user);
String string = new ObjectMapper().writeValueAsString(ok);
writer.write(string);
writer.flush();
writer.close();
}
})
......
}
其中
http.authorizeRequests().anyRequest().authenticated()
表明所有請求都需要登錄過后才能訪問。
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/doLogin")
.loginPage("/login")
配置登錄表的 用戶名、密碼、處理登錄的頁面、登錄成功的頁面。
登錄成功后:
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
User user = (User) authentication.getPrincipal();
user.setPassword(null);
RespBean ok = RespBean.ok("登錄成功!", user);
String string = new ObjectMapper().writeValueAsString(ok);
writer.write(string);
writer.flush();
writer.close();
這些配置幾乎都是固定的,直接 copy 即可。
還有登錄失敗的配置、注銷登錄的配置,可以參考文章:Spring Security 基礎教程 -- HttpSecurity 權限和登錄表單配置
配置密碼加密的類
由於我的數據庫中 User 類的密碼采用的是 md5DigestAsHex 加密,沒有用 SpringSecurity 自帶的加密方式 BCryptPasswordEncoder。
所以需要自定義加密類,照樣在 config 包下,新建 MyPasswordEncoder 類:
@Component
public class MyPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(DigestUtils.md5DigestAsHex(charSequence.toString().getBytes()));
}
}
配置登錄成功后的頁面
由於 SpringSecurity 在登錄成功后,會自動跳轉頁面,而在 Spring Boot + Vue 前后端分離項目中,跳轉頁面是前端的事,后端只返回 json 數據即可。
所以需要額外配置一下。
在 controller 包下新建類 LoginController :
@RestController
public class LoginController {
@GetMapping("/login")
public RespBean login(){
return RespBean.error("尚未登錄,請登錄!");
}
}
測試登錄接口
一切准備就緒,需要測試一下效果。
在 controller 包下新建類 HelloController:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello";
}
}
這里定義了一個訪問接口 /hello
,最后用 postman 測試。
postman 測試
- 啟動項目,在 postman訪問 http://localhost:8081/hello,效果如下:
- 登錄操作
- 再訪問接口
/hello
- 注銷登錄
注銷登錄是 get 請求,默認接口時 logout :
每天學習一點點,每天進步一點點。