Java結合SpringBoot攔截器實現簡單的登錄認證模塊
之前在做項目時需要實現一個簡單的登錄認證的功能,就尋思着使用Spring Boot的攔截器來實現,在此記錄一下我的整個實現過程,源碼見文章底部。
1. 環境搭建
IntelliJ IDEA + Java8 + Spring Boot + Tomcat
我將之前項目中的登錄模塊抽離出來,單獨放在了一個新建的Spring Boot項目中;
整個項目的主要結構如下:
2. 代碼詳解
2.1 前端代碼
之前項目里別的小伙伴已經寫好了一個簡單的登錄框樣式表(login.css)和一些image圖,我這里就順手拿來用了,希望哪天你見了眼熟別拍我…
login.vm代碼:
注意前端傳遞給后端Controller的password值並不是用戶實際輸入的密碼!
實際傳遞的是用戶名 + 密碼(統一小寫)組合的字符串的md5信息值;
這樣在前后台數據傳遞及后台數據保存時傳遞和保存的都不是用戶的真實密碼值,可以一定程度提升安全性及規避某些風險;
更多資料可參考:Web前端密碼加密是否有意義
<html>
<head>
<title>系統登錄</title>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="author" content="Dreamer-1">
<meta name="renderer" content="webkit" />
<link href="/css/login/login.css" rel="stylesheet">
<script type="text/javascript" src="/js/login/jquery/jquery.min.js?v=20170207"></script>
<script type="text/javascript" src="/js/login/md5/md5.js"></script>
</head>
<body>
<form name="form1" method="post" action="/login" id="form1" onsubmit="return checkLogin();">
<div id="main">
<div class="wrapper">
<div class="login-hd"></div>
<div class="login-body">
<div class="logo">
<span class="icon-logo"></span>
</div>
<div class="box">
<div class="login-item">
<span class="icon-user"></span>
<input name="username" type="text" id="username" class="login-input" tabindex="1" maxlength="50" placeholder="請輸入用戶名" />
</div>
<div class="login-item mt35">
<span class="icon-pwd"></span>
<input type="password" id="password" class="login-input" tabindex="2" maxlength="32" placeholder="請輸入密碼"/>
<input type="hidden" id="hidePwd" name="password">
</div>
<div class="login-forget" style="visibility:hidden">
<a href="#">忘記密碼</a>
</div>
<input type="submit" name="Logon" value="登錄" id="Logon" tabindex="3" class="login-btn" />
<div class="login-bottom">
<div class="msg" style="display:none;" >
<span class="icon-err"></span>
<span id="message"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
<script type="text/javascript">
// onsubmit值為true時,提交表單,否則顯示錯誤信息
// 生成用戶名+密碼組合的md5值,並設置傳給后端的密碼為該md5值
function checkLogin() {
var name = $("#username").val().toLowerCase();
var pwd = $("#password").val().toLowerCase();
if(name.trim()=="" || pwd.trim()=="") {
$("#message").text("請輸入用戶名和密碼");
$('.msg').show();
return false;
}else {
$('.msg').hide();
}
var md5info = name + pwd;
$('#hidePwd').val(md5(md5info));
//$("#password").val();
return true;
}
</script>
</body>
</html>
welcome.vm代碼
登錄成功后顯示welcome.vm頁的內容,這個頁面很簡單:
<h1 align="center">登錄成功!!!</h1>
<br>
<h3><a href="/loginout"><font color="red">退出登錄</font></a></h3>
2.2 后端代碼
后端代碼相較於前端要復雜一些,讓我們來一一拆解;
2.2.1 程序入口
ManApplication.java是整個程序的主入口,因為其上打了@SpringBootApplication的注解;
注意:Spring Boot項目在tomcat上部署運行時,ManApplication需要繼承SpringBootServletInitializer
類
ManApplication.java代碼:
/**
* @SpringBootApplication 注解標明該類是本程序的入口
*/
@SpringBootApplication
public class ManApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ManApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(ManApplication.class, args);
}
}
2.2.2 Controller
IndexViewController.java類里就是簡單的URL映射;
/**
* Created with logindemo.
* Author: dreamer-1
* Email: zhong--lei@outllok.com
* Date: 2018/5/13
* Time: 下午2:58
* Description:
*/
@Controller
public class IndexViewController {
/**
* 登錄
* @return
*/
@GetMapping("/")
public String index() {
return "login";
}
/**
* 歡迎頁
* @return
*/
@GetMapping("/welcome")
public String welcome() {
return "welcome";
}
}
LoginViewController.java類接收前端傳過來的username和password,進行簡單的校驗和重定向;
此處為了簡單就只設置了一個正確的賬號和密碼用於校驗,你后續使用時可以結合自己的實際需求來擴充整個校驗邏輯(比如通過專門的表來存儲用戶登錄信息等);
用戶名和密碼校驗通過后會在當前會話的session中放入一個登錄標識,以表示當前用戶已經登錄;在退出登錄或會話超時時銷毀該標識;
/**
* Created with logindemo.
* Author: dreamer-1
* Email: zhong--lei@outllok.com
* Date: 2018/5/13
* Time: 下午2:49
* Description:
*/
@Controller
public class LoginViewController {
// 預先設置好的正確的用戶名和密碼,用於登錄驗證
private String rightUserName = "admin";
private String rightPassword = "admin";
/**
* 登錄校驗
*
* @param request
* @return
*/
@RequestMapping("/login")
public String login(HttpServletRequest request) {
String username = request.getParameter("username");
String password = request.getParameter("password");
if (null == username || null == password) {
return "redirect:/";
}
// 前端傳回的密碼實際為用戶輸入的:用戶名(小寫)+ 密碼(小寫)組合的字符串生成的md5值
// 此處先通過后台保存的正確的用戶名和密碼計算出正確的md5值,然后和前端傳回來的作比較
String md5info = rightUserName.toLowerCase() + rightPassword.toLowerCase();
String realPassword = DigestUtils.md5DigestAsHex(md5info.getBytes());
if (!password.equals(realPassword)) {
return "redirect:/";
}
// 校驗通過時,在session里放入一個標識
// 后續通過session里是否存在該標識來判斷用戶是否登錄
request.getSession().setAttribute("loginName", "admin");
return "redirect:/welcome";
}
/**
* 注銷登錄
*
* @param request
* @return
*/
@RequestMapping("/loginout")
public String loginOut(HttpServletRequest request) {
request.getSession().invalidate();
return "redirect:/";
}
}
2.2.3 Interceptor
LoginInterceptor.java是整個登錄認證模塊中的核心類之一,它實現了HandlerInterceptor
類,由它來攔截並過濾到來的每一個請求;它的三個方法能分別作用於每個請求的不同生命周期,你可以根據自己的需要來加入相應的處理邏輯;
/**
* Created with logindemo.
* Author: dreamer-1
* Email: zhong--lei@outllok.com
* Date: 2018/5/13
* Time: 下午2:58
* Description:
*/
public class LoginInterceptor implements HandlerInterceptor {
/**
* 在請求被處理之前調用
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 檢查每個到來的請求對應的session域中是否有登錄標識
Object loginName = request.getSession().getAttribute("loginName");
if (null == loginName || !(loginName instanceof String)) {
// 未登錄,重定向到登錄頁
response.sendRedirect("/");
return false;
}
String userName = (String) loginName;
System.out.println("當前用戶已登錄,登錄的用戶名為: " + userName);
return true;
}
/**
* 在請求被處理后,視圖渲染之前調用
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* 在整個請求結束后調用
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
2.2.4 Configuration
LoginConfiguration.java是另一個核心類之一,它實現了WebMvcConfigurer
類,負責注冊並生效我們自己定義的攔截器配置;
在這里要注意定義好攔截路徑和排除攔截的路徑;
WebMvcConfigurerAdapter
其實還可以做很多其他的事,包括添加自定義的視圖控制器等等,詳見這里
/**
* Created with logindemo.
* Author: dreamer-1
* Email: zhong--lei@outllok.com
* Date: 2018/5/13
* Time: 下午2:58
* Description:
*/
@Configuration
public class LoginConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注冊攔截器
LoginInterceptor loginInterceptor = new LoginInterceptor();
InterceptorRegistration loginRegistry = registry.addInterceptor(loginInterceptor);
// 攔截路徑
loginRegistry.addPathPatterns("/**");
// 排除路徑
loginRegistry.excludePathPatterns("/");
loginRegistry.excludePathPatterns("/login");
loginRegistry.excludePathPatterns("/loginout");
// 排除資源請求
loginRegistry.excludePathPatterns("/css/login/*.css");
loginRegistry.excludePathPatterns("/js/login/**/*.js");
loginRegistry.excludePathPatterns("/image/login/*.png");
}
}
3. 踩坑與填坑
3.1 多次重定向與Circle view path錯誤
剛開始程序部署至tomcat里運行時,理所當然的出現了意想不到的情況,詳情如下:
啟動時localhost:8080
顯示:
后台一直報錯:
通過斷點調試,發現啟動后不停地進入IndexViewController中的“/”這個URL映射里;
手動指定訪問路徑為 localhost:8080/welcome
時后台報錯:
解決辦法:
- 在pom文件中導入thymeleaf依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 在application.properties里添加如下配置:
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.vm
我猜想可能是我的view文件都以.vm結尾,thymeleaf默認找的是.html結尾的視圖,所以一直找不到;
有知道的大神可以在下面留言詳細講解一下 _
4. 大功告成
4.1 運行截圖
未登錄情況下訪問 localhost:8080/welcome
等非登錄頁面時會自動跳轉到登錄頁面: