之前寫過一篇關於Springboot+Shiro實現前後端分離的權限管理系統。但是由於使用了框架,對於一些小系統,實在用不上Shiro,而且還要加上學習成本。今天就來用原生的Spring技術實現登錄攔截。
前後端分離
要實現前後端分離,需要考慮以下2個問題: 1. 項目不再基於session了,如何知道訪問者是誰? 2. 如何確認訪問者的權限?
前後端分離,一般都是通過token實現,本項目也是一樣;用戶登錄時,生成token及 token過期時間,token與用戶是一一對應關係,調用接口的時候,把token放到header或 請求參數中,服務端就知道是誰在調用接口。
源碼先雙手奉上:https://github.com/FENGZHIJIE1998/Auth-demo
各位客官覺得好用記得給個Star!
項目架構圖
導包、配置文件都省略。
第一步編寫自己的攔截器AuthInteceptor
public class AuthInterceptor implements HandlerInterceptor {
@Autowired
private AuthService authService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
String token = TokenUtil.getRequestToken(request);
//如果token爲空
if (StringUtils.isBlank(token)) {
setReturn(response,400,"用戶未登錄,請先登錄");
return false;
}
//1. 根據token,查詢用戶信息
UserEntity userEntity = authService.findByToken(token);
//2. 若用戶不存在,
if (userEntity == null) {
setReturn(response,400,"用戶不存在");
return false;
}
//3. token失效
if (userEntity.getExpireTime().isBefore(LocalDateTime.now())) {
setReturn(response,400,"用戶登錄憑證已失效,請重新登錄");
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
}
//返回錯誤信息
private static void setReturn(HttpServletResponse response, int status, String msg) throws IOException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtil.getOrigin());
//UTF-8編碼
httpResponse.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=utf-8");
Result build = Result.build(status, msg);
String json = JSON.toJSONString(build);
httpResponse.getWriter().print(json);
}
}
第二步,將攔截器配置進Spring
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public AuthInterceptor authInterceptor() {
return new AuthInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 放行路徑
List<String> patterns = new ArrayList();
patterns.add("/webjars/**");
patterns.add("/druid/**");
patterns.add("/sys/login");
patterns.add("/swagger/**");
patterns.add("/v2/api-docs");
patterns.add("/swagger-ui.html");
patterns.add("/swagger-resources/**");
patterns.add("/login");
registry.addInterceptor(authInterceptor()).addPathPatterns("/**")
.excludePathPatterns(patterns);
}
}
第三步:測試
Controller 層
/**
* 登錄校驗
*/
@RestController("/")
public class AuthController {
@Autowired
private AuthService authService;
/**
* 登錄
*
* @param loginDTO
* @return token登錄憑證
*/
@PostMapping("/login")
public Result login(@RequestBody LoginDTO loginDTO) {
String username = loginDTO.getUsername();
String password = loginDTO.getPassword();
//用戶信息
UserEntity user = authService.findByUsername(username);
//賬號不存在、密碼錯誤
if (user == null || !user.getPassword().equals(password)) {
return Result.build(400, "用戶名或密碼錯誤");
} else {
//生成token,並保存到數據庫
String token = authService.createToken(user);
TokenVO tokenVO = new TokenVO();
tokenVO.setToken(token);
return Result.ok(tokenVO);
}
}
/**
* 登出
*
* @param
* @return
*/
@PostMapping("/logout")
public Result logout(HttpServletRequest request) {
//從request中取出token
String token = TokenUtil.getRequestToken(request);
authService.logout(token);
return Result.ok();
}
/**
* 測試
*
* @param
* @return
*/
@PostMapping("/test")
public Result test( ) {
return Result.ok("恭喜你,驗證成功啦,我可以返回數據給你");
}
}
編寫Service層
@Service
public class AuthServiceImpl implements AuthService {
@Autowired
private UserRepository userRepository;
@Override
public UserEntity findByUsername(String username) {
return userRepository.findByUsername(username);
}
//12小時後失效
private final static int EXPIRE = 12;
@Override
public String createToken(UserEntity user) {
//用UUID生成token
String token = UUID.randomUUID().toString();
//當前時間
LocalDateTime now = LocalDateTime.now();
//過期時間
LocalDateTime expireTime = now.plusHours(EXPIRE);
//保存到數據庫
user.setLoginTime(now);
user.setExpireTime(expireTime);
user.setToken(token);
userRepository.save(user);
return token;
}
@Override
public void logout(String token) {
UserEntity userEntity = userRepository.findByToken(token);
//用UUID生成token
token = UUID.randomUUID().toString();
userEntity.setToken(token);
userRepository.save(userEntity);
}
@Override
public UserEntity findByToken(String token) {
return userRepository.findByToken(token);
}
}
然後啓動項目即可
首先數據庫保存一個用戶,用戶管理這裏就不做啦。
我們來看看效果:使用Swagger查看效果
未登錄狀態下:
傳遞錯誤的token:
登錄成功:
傳遞正確的token:
token過期之後
總結:如果你看過我那篇關於Shiro前後端分離的架構,其實很容易發現,這不就是簡化版的Shiro嗎?攔截、驗證、返回錯誤信息。這就是思想上的一致。Shiro底層是用Filter(過濾器)、HandlerInterceptor底層是Interceptor(攔截器)。此外,本篇不僅僅可以實現登錄攔截,對於權限攔截也是妥妥的,只需要再寫一個攔截器,對權限進行驗證,這裏就不展開了。最主要還是前後端分離的思想,由前端控制路由,後端只負責返回對應信息,這肯定是發展趨勢。
有什麼問題可以評論或者私信我,每日在線解(LIAO)疑(SAO)。
我是大誌,一位準備996的卑微碼農🐶,覺得好用記得點贊收藏!!!