tags: 從零開發項目javascript
我要將其弄成相似的登錄,功能是要比較完善的。css
原本我是想作一步寫一步的,可是發現這樣文章就會太亂,由於要改的地方太多了。前面寫過的,後邊就被修改了。這樣看起來太混亂了。所以我如今是寫完代碼纔來補這篇記錄的。儘量把當時的思路記錄下來,而且捋一捋看一下有沒有不足的地方。html
個人登錄註冊模塊主要是使用郵箱,這部分以前是沒有了解過的。所以去找了一點資料。想完成使用郵箱來激活用戶的思路可看下面:java
blog.csdn.net/u013863751/…jquery
根據這個思路,我就建立數據庫表了,數據庫表的設計以下:git
設計完數據庫表以後就去寫前臺頁面了。由於登錄註冊這部分的Dao層並不難,就幾個簡單的方法。github
前臺我使用的是Bootstrap爲主,登錄註冊模塊是使用bootstrap官網提供的模版。web
導航欄是使用以前看見過別人博客上的導航條:ajax
blog.csdn.net/iamcgt/arti…正則表達式
有了導航條和bootstrap官網提供的登錄註冊模版了,背景如今太過單薄了。我想到了一個開源的項目:github.com/VincentGarr…。因而下載下來改了一下。個人首頁樣式就有了。
particles.js參考教程:
使用時要注意的是:particles div中包含的內容須要絕對定位。
註冊的時候須要作前臺校驗的。原本是想本身寫正則表達式作校驗的,後來發現有BootstrapValidation這樣一個組件。因而就去下載使用了。
boostrapValidation參考教程:
bootstrapValidation有一個坑,**submitHandler提交方法只有0.45版本纔有效(親測)。**後來在尋找版本的時候發現了一個很是有用的網站:可以查詢到多個版本,十分好用。
通過一頓整改,個人頁面和驗證效果以下:
作了前臺的校驗是不夠的。由於有可能別人是知道咱們註冊的url就直接對其進行訪問了(繞過bootstrapValidation來註冊),所以咱們還須要作後臺校驗。後臺校驗我使用的是SpringMVC的校驗。也就是 Hibernate Validator(和Hibernate的ORM無關)。
要使用springMVC校驗就要導入對應的maven座標:
<!--spring Validation校驗-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.3.0.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.1.0.CR2</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.0.0.GA</version>
</dependency>
複製代碼
參考資料:
zhongfucheng.bitcron.com/post/spring…
在寫配置文件的時候雖然idea提示是紅色的,可是無妨,仍是能夠或獲取獲得的。對其忽略就行
那麼咱們的後臺校驗也已經完成了。
還有須要作校驗的就是,若是用戶已經註冊過了,咱們就不能讓它再註冊了。爲了達到更好的用戶體檢,在提交表單前使用ajax來作校驗就好了
//郵箱去作後臺惟一性校驗
submitHandler: function (validator, form, submitButton) {
var $email = $("#userEmail").val();
console.log($email);
$.ajax({
url: path + "/user/validateEmail.do",
type: "post",
async: false,
data: {
"userEmail": $email
},
success: function (responseText) {
if (responseText == "noEmail") {
validator.defaultSubmit();
sweetAlert("請到您指定的郵箱完成激活");
} else {
sweetAlert("您的郵箱已註冊過了");
}
},
error: function () {
sweetAlert("系統錯誤!");
}
});
}
複製代碼
作這個惟一性校驗其實就是比對數據庫有沒有該條郵箱的記錄罷了。
在尋找資料的時候發現了sweetAlert這麼一個組件,發現比瀏覽器自帶的alert要好看得多。所以又去下載來用了。值得一提的是,我使用的環境是windows下載zip的方式,**在官網下載的css文件是自帶有錯的。**後來仍是去上面我說的那個網站直接找出它的css和js文件就行了。
資料以下:
效果以下:
搞了那麼久,就剩下入數據庫了。
/** * 先對數據進行校驗,再註冊 * * @param user * @return * @throws Exception */
@RequestMapping("/register.do")
public String register(@Validated User user, BindingResult bindingResult) throws Exception {
//若是參數不對,就直接返回註冊頁面
List<ObjectError> allErrors = bindingResult.getAllErrors();
if (allErrors != null && allErrors.size() > 0) {
return "redirect:/goURL/user/toRegister.do";
}
//對密碼進行加密md5(密碼+salt)後才存到數據庫中
userService.encryptedPassword(user);
userService.insert(user);
//提示用戶發送了郵件,讓用戶激活帳戶
String url = getProjectPath() + "/user/activate.do?userId=" + user.getUserId();
emailService.sendEmail(user, "註冊", url);
return "redirect:/common/countDown.html";
}
複製代碼
首次插入進數據庫的時候,激活碼默認值是0,還有uuid生成
<!--insert被自定義了。-->
<insert id="insert" parameterType="zhongfucheng.entity.User" >
<selectKey keyProperty="userId" order="BEFORE" resultType="string">
select uuid()
</selectKey>
insert into table_user (user_id, user_nickname, user_password,
user_email, acti_state, acti_code,
salt,token_exptime)
values (#{userId}, #{userNickname,jdbcType=VARCHAR}, #{userPassword,jdbcType=VARCHAR},
#{userEmail,jdbcType=VARCHAR}, 0, uuid(),
#{salt},now())
</insert>
複製代碼
對密碼加密也很簡單:生成一個隨機數做爲salt,使用md5(用戶傳遞進來的密碼+salt)
發送郵件就涉及到了javaMail了,javaMail以前是沒有接觸過的。因而就去找了幾篇資料:
我是使用qq郵箱去發送郵件的:要去qq郵箱申請受權碼才能發送
在看上邊資料的時候,發現郵件其實用freemarker來作模版會很不錯(由於郵箱的只有少部份內容是變的),因而又去找freemarker與spring整合的資料:
上訴的maven座標:
<!-- Javamail與Spring-context-support support包也與freemarker整合 -->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
<!--freemarker-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.18</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
複製代碼
首先構建出發送郵件的模版數據、而後使用JavaMail發送帶HTML格式的數據就好了。
/** * 郵件服務類,提供發送郵件的功能 */
@Service
public class EmailService {
@Autowired
private JavaMailSender mailSender;
@Autowired
private SimpleMailMessage simpleMailMessage;
/** * 使用mimeMessage發送的HTML格式的郵件。 * @param user * @param content * @throws Exception */
public void sendEmail(User user, String content,String url) throws Exception {
String returnText = createSendData(user, content,url);
// TODO 問題是出在發送郵件很慢 6086ms,解析freemarker才60ms 待優化
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
messageHelper.setFrom(simpleMailMessage.getFrom());
messageHelper.setSubject(simpleMailMessage.getSubject());
//接受人
messageHelper.setTo(user.getUserEmail());
//內容,是HTML格式
messageHelper.setText(returnText, true);
mailSender.send(mimeMessage);
}
/** * 使用freemarker建立要發送的郵件內容 * @param user 封裝了要發送郵件的信息 * @param content 發送的目的是什麼(一個模版、多種郵件) * @param url 操做的地址路徑是什麼 * @return HTML頁面的內容 * @throws Exception */
public String createSendData(User user, String content,String url) throws Exception {
Map<String, Object> map = new HashMap();
map.put("nickName", user.getUserNickname());
map.put("content", content);
map.put("url", url);
map.put("encodeUrl", Base64Util.encodeData(url));
String returnText = new FreeMarkerUtils().returnText("email.ftl", map);
return returnText;
}
}
複製代碼
freemarker在springmvc配置文件中的配置:
<!-- 同時開啓json格式的支持 -->
<mvc:annotation-driven></mvc:annotation-driven>
<!-- 掃描全部的controller 可是不掃描service -->
<context:component-scan base-package="zhongfucheng">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
<!-- 配置Freemarker屬性文件路徑 -->
<bean id="freemarkerConfiguration" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="classpath:freemarker.properties"/>
</bean>
<!-- 配置freeMarker模板加載地址 -->
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<!-- 視圖解析器會在/WEB-INF/ftl/路徑下掃描視圖文件 -->
<property name="templateLoaderPath" value="/WEB-INF/ftl"/>
<!-- 設置頁面中文亂碼問題 -->
<property name="freemarkerSettings">
<props>
<prop key="defaultEncoding">UTF-8</prop>
</props>
</property>
<property name="freemarkerVariables">
<map>
<entry key="xml_escape" value-ref="fmXmlEscape"/>
</map>
</property>
</bean>
<bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape"/>
<!-- 配置freeMarker視圖解析器 -->
<bean id="freemakerViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.freemarker.FreeMarkerView"/>
<!-- 掃描路徑內全部以ftl結尾的文件 -->
<property name="viewNames">
<array>
<value>*.ftl</value>
</array>
</property>
<!-- 設置相關屬性 -->
<property name="cache" value="true"/>
<property name="contentType" value="text/html; charset=UTF-8"/>
<property name="exposeRequestAttributes" value="true"/>
<property name="exposeSessionAttributes" value="true"/>
<property name="exposeSpringMacroHelpers" value="true"/>
<property name="requestContextAttribute" value="request"/>
<!-- 給視圖解析器配置優先級,你能夠給以前jsp視圖解析器的值配為2 -->
<property name="order" value="1"/>
</bean>
<!--通用視圖解析器-->
<bean id="viewResolverCommon" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass">
<value>org.springframework.web.servlet.view.InternalResourceView
</value>
</property>
<property name="order" value="2"/>
</bean>
複製代碼
在設置freemaker路徑的時候發現本身對Java路徑的相對路徑、絕對路徑、項目路徑已有些混亂了。後來經過一篇資料又好好地整理一下:
不少時候咱們的項目路徑在不一樣機器上是不同的。所以要作到更好的通用性,能夠將其在配置文件中配置起來。
提供一個工具類提取它就好了:
/** * 根據key讀取配置文件的內容 * */
public class ReadPropertiesUtil {
public static String readProp(String key) {
InputStream in = ReadPropertiesUtil.class.getClassLoader().getResourceAsStream("system.properties");
Properties prop = new Properties();
try {
prop.load(in);
} catch (IOException e) {
e.printStackTrace();
}
return prop.getProperty(key);
}
}
複製代碼
扯了這麼一大堆,咱們的郵件已經可以發出去了
url連接使用了base64進行編碼了,其實沒什麼,就是爲了裝個逼而已..
maven座標:
<!--base64編碼解碼-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.6</version>
</dependency>
複製代碼
base64工具類:
/** * base64的編碼解碼 */
public class Base64Util {
private static final String UTF_8 = "UTF-8";
/** * 對給定的字符串進行base64解碼操做 */
public static String decodeData(String inputData) {
try {
if (null == inputData) {
return null;
}
return new String(Base64.decodeBase64(inputData.getBytes(UTF_8)), UTF_8);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
/** * 對給定的字符串進行base64加密操做 */
public static String encodeData(String inputData) {
try {
if (null == inputData) {
return null;
}
return new String(Base64.encodeBase64(inputData.getBytes(UTF_8)), UTF_8);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
}
複製代碼
那麼接下來就是點擊鏈接激活帳戶了,修改一下激活碼值就好了。從上邊咱們已經寫到了:激活連接24小時內有效,若是超過了一天用戶再點擊激活碼的時候,那麼咱們就認爲它無效,把數據庫的記錄刪除了,讓他從新註冊。
/** * 激活帳戶(實際上就是修改表字段的值) * * @param userId * @return * @throws Exception */
@RequestMapping("/activate.do")
public String activate(String userId) throws Exception {
User user = userService.selectByPrimaryKey(userId);
String title = "";
String content = "";
String subject = "";
if (user != null) {
//獲得當前時間和郵件時間對比,24小時內
if (System.currentTimeMillis() - user.getTokenExptime().getTime() < 86400000) {
user.setActiState(User.ACTIVATION_SUCCESSFUL);
userService.updateByPrimaryKeySelective(user);
title = "用戶激活頁面";
subject = "用戶激活";
content = "恭喜您成功激活帳戶";
} else {
title = "激活失敗頁面";
subject = "用戶激活";
content = "激活連接已超時,請從新註冊";
//刪除記錄已便用戶再次註冊
userService.deleteByPrimaryKey(userId);
}
}
//根據模版生成頁面,重定向到頁面中
Map<String, Object> map = new HashedMap();
map.put("title", title);
map.put("content", content);
map.put("subject", subject);
map.put("path", getProjectPath());
createCommonHtml("promptPages.ftl", "promptPages.html", map);
return "redirect:/promptPages.html";
}
複製代碼
好了,到如今爲止註冊模塊就寫完了。咱們來看一下實際的效果:
編寫登錄模塊是用了我比較多的時間的,由於用了首次用了Shiro框架來進行作驗證。當時候學的時候並非學得很深刻,因而出現了不少bug,改了幾天才把它捋順了。
登錄頁面和註冊頁面實際上是很是相似的,具體的步驟都和註冊頁面差很少。只不過我在登錄頁面中加入了一個驗證碼:
該驗證碼是動態的gif,是我以前看github項目的時候發現的。以爲挺好看的就拿過來用了。要想使用它就要導入它的相關java類:
來源github項目:github.com/baichengzho…
我引入了Shiro框架來幫我作認證...
maven座標:
<!--Shiro與Spring整合-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.3</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<!--Shiro與ehcache整合-->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
複製代碼
首先來post一下個人基礎shiro博文:
zhongfucheng.bitcron.com/category/sh…
想要在shiro認證以前可以校驗驗證碼的話,那麼就須要咱們去自定義表單過濾器了。
/** * 自定義一個表單過濾器的目的就是認證流程由本身控制 */
public class UserFormAuthenticationFilter extends FormAuthenticationFilter {
}
複製代碼
當時候我重寫了onAccessDenied()方法,在認證以前去校驗驗證碼的正確性,而且使用ajax來進行提示用戶是否有錯誤信息:
大體的錯誤代碼以下:
/** * 用戶登錄,Shiro從Reaml中驗證,返回JSON提示用戶 * * @param request * @return * @throws Exception */
@RequestMapping("/login.do")
@ResponseBody
public Map<String, Object> login(HttpServletRequest request) throws Exception {
Map<String, Object> resultMap = new LinkedHashMap<String, Object>();
//若是登錄失敗從request中獲取認證異常信息,shiroLoginFailure就是shiro異常類的全限定名
String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
//根據shiro返回的異常類路徑判斷,拋出指定異常信息
if (exceptionClassName != null) {
if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
resultMap.put("message", "帳號不存在");
} else if (IncorrectCredentialsException.class.getName().equals(
exceptionClassName)) {
resultMap.put("message", "用戶名/密碼錯誤");
} else if ("captchaCodeError".equals(exceptionClassName)) {
resultMap.put("message", "驗證碼錯誤");
} else {
throw new Exception();//最終在異常處理器生成未知錯誤
}
} else {
resultMap.put("message", "登錄成功");
//把用戶信息寫到session中
Subject subject = SecurityUtils.getSubject();
ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
request.getSession().setAttribute("activeUser", activeUser);
}
return resultMap;
}
複製代碼
$.ajax({
url: path + "/user/login.do",
type: "post",
async: false,
data: $("#loginForm").serialize(),
success: function (responseText) {
console.log(responseText);
if(responseText.message=="驗證碼錯誤"){
alert("驗證碼錯誤");
}else if(responseText.message=="帳號不存在") {
alert("帳號不存在");
}else if(responseText.message=="用戶名/密碼錯誤") {
alert("用戶名/密碼錯誤");
}else if(responseText.message=="登錄成功") {
window.location.href = path + "/index.html";
}else {
console.log(responseText);
alert("未知錯誤");
}
},
error: function () {
alert("系統錯誤");
}
})
複製代碼
在測試的時候就有很是怪異的想象:登錄的時候,有時能夠返回正常JSON的信息、有的時候直接不調用ajax(後臺打斷點並無進入後臺),可是可以通過sucess方法返回一個頁面的內容。
這就令我感到很是驚奇了,因而乎,我一直在搜索「爲何ajax不調用、success方法卻回調了」、」sucess回調方法返回一個頁面「、」ajax常見錯誤「。顯然,**我一直認爲是ajax的問題,並無懷疑Shiro的認證流程。**在此方面花費我不少的時間,我懷疑過jquery的版本,還有其餘方面的衝突.....
直到後來我就在想:爲何有的時候JSON返回一個頁面的內容呢???此時我想起Shiro的認證流程了。若是認證不經過,Shiro默認返回給login.do處理,若是驗證經過,shiro默認返回上一級請求的url。
也就是說:**我在login.do中返回一個JSON是不合理的。**由於若是沒有認證的話,Shiro默認是須要返回登錄界面的,而我擅自修改爲JSON了。因而就形成了奇怪的現象了。
那問題又來了,若是認證失敗的話,爲了作到更好的用戶體驗是須要實時地告訴用戶哪裏錯了,而不是直接返回頁面內容。用戶不知道還會一臉懵逼。
由於login.do是專門處理異常的信息的,所以咱們可使用統一處理異常的方式去處理:
if (exceptionClassName != null) {
if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
throw new UserException("帳號不存在");
} else if (IncorrectCredentialsException.class.getName().equals(
exceptionClassName)) {
throw new UserException("密碼錯誤了");
} else if ("captchaCodeError".equals(exceptionClassName)) {
throw new UserException("驗證碼錯誤了");
} else {
throw new Exception();//最終在異常處理器生成未知錯誤
}
}
return "redirect:/goURL/user/toLogin.do";
複製代碼
統一異常處理器:
/** * 統一異常處理類 */
public class SysException implements HandlerExceptionResolver {
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object o, Exception ex) {
//輸出異常
ex.printStackTrace();
String message = null;
UserException userException = null;
//若是ex是系統 自定義的異常,直接取出異常信息
if (ex instanceof UserException) {
userException = (UserException) ex;
} else {
//針對非UserException異常,對這類從新構形成一個UserException,異常信息爲「未知錯誤」
userException = new UserException("未知錯誤");
}
message = userException.getMessage();
request.setAttribute("message", message);
try {
//根據模版生成頁面,重定向到頁面中
Map<String, Object> map = new HashedMap();
map.put("title", "錯誤頁面");
map.put("content", message);
map.put("subject", "出錯啦");
map.put("path", ReadPropertiesUtil.readProp("projectPath"));
FreeMarkerUtils markerUtils = new FreeMarkerUtils();
markerUtils.ouputFile("promptPages.ftl", "promptPages.html", map);
request.getRequestDispatcher("/promptPages.html").forward(request, response);
} catch (Exception e) {
e.printStackTrace();
}
return new ModelAndView();
}
}
複製代碼
上面已經解決了提示錯誤信息的問題了。但是我以爲不夠好,由於錯誤信息跳轉到別的頁面了,用戶須要從新回到登錄頁面進行註冊,這個也太麻煩了吧。ps(要是我登錄錯誤搞這麼一個東西,我就認爲這個是破網站...)。
因而乎,我就想在**怎麼實時把錯誤信息返回給登錄頁面呢??ajax是否還能用呢??login方法是必定要返回一個頁面的了。**試了不少無用的方法,在網上也找不到相關的方法,當時搜索關鍵字」Shiro返回錯誤信息「.....。
此時,我就在想ajax和Shiro是否能結合起來...後來去**搜索了一下」ajax和Shiro「**才知道網上也有人遇到我這種狀況。
參考資料:
大體的思路就知道了:重寫自定義表單過濾器的方法,判斷是否爲ajax請求來進行處理
期間找了不少相關的資料,每一個人的實現都良莠不齊。表單過濾器方法中的retrun true
和return false
也把我搞得一頭霧水。最後,迴歸到搜索關鍵字「Shiro認證流程「,找到了一篇解決我問題的博文:
通過上面的資料和閱讀了Shiro相關的源碼,我基本能知道shiro認證流程了,下面是我畫的一張流程圖:
根據流程能夠判斷在驗證碼失敗時若是是ajax請求返回JSON數據。若是登錄失敗,重寫onLoginFailure方法,也判斷是否爲ajax請求。,
package zhongfucheng.shiro;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import zhongfucheng.entity.ActiveUser;
import zhongfucheng.utils.ReadPropertiesUtil;
import zhongfucheng.utils.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** * Created by ozc on 2017/10/27. */
/** * 自定義一個表單過濾器的目的就是認證流程由本身控制 */
public class UserFormAuthenticationFilter extends FormAuthenticationFilter {
/** * 只要請求地址不是post請求和不是user/login(處理登錄的url),那麼就返回登錄頁面上 * * @param request * @param response * @return * @throws Exception */
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
//判斷是不是登錄頁面地址請求地址、若是不是那麼重定向到controller的方法中
if (isLoginRequest(request, response)) {
if (isLoginSubmission(request, response)) {
//在提交給realm查詢前,先判斷驗證碼
if (WebUtils.validateCaptcha(httpRequest)) {
return executeLogin(request, response);
} else {
if (isAjax(httpRequest)) {
//這裏要使用標準的JSON格式
WebUtils.printCNJSON("{\"message\":\"驗證碼錯誤\"}", httpServletResponse);
return false;
} else {
// 放行 allow them to see the login page ;)
httpRequest.setAttribute("shiroLoginFailure", "captchaCodeError");
return true;
}
}
} else {
// 放行 allow them to see the login page ;)
return true;
}
} else {
// TODO AJAX請求用戶擴展。之後再補
if (isAjax(httpRequest)) {
//httpServletResponse.sendError(ShiroFilterUtils.HTTP_STATUS_SESSION_EXPIRE);
return false;
} else {
//返回配置的user/login.do,該方法會重定向到登錄頁面地址,再次發送請求給本方法
saveRequestAndRedirectToLogin(request, response);
}
return false;
}
}
/** * 認證成功,把用戶認證信息保存在session中,判斷是否爲ajax請求 * @param token * @param subject * @param request * @param response * @return * @throws Exception */
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
//在跳轉前將數據保存到session中
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
WebUtils.setValue2Session(httpRequest, "activeUser", activeUser);
//若是是ajax請求,那麼咱們手動跳轉
//若是不是ajax請求,那麼由Shiro幫咱們跳轉
if (isAjax(httpRequest)) {
WebUtils.printCNJSON("{\"message\":\"登錄成功\"}", httpServletResponse);
} else {
//設置它跳轉到首頁路徑,若是不設置它還會停留在登錄頁面。
String indexPath = ReadPropertiesUtil.readProp("projectPath") + "/index.html";
org.apache.shiro.web.util.WebUtils.redirectToSavedRequest(request, response, indexPath);
}
return false;
}
/** * 認證失敗、若是ajax請求則返回json數據 * 若是非ajax請求、則默認返回給login.do處理異常 * @param token * @param e * @param request * @param response * @return */
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
// 不是ajax請求,就按照源碼的方式去幹(返回異常給controller,controller處理異常)
if (!isAjax(httpServletRequest)) {
setFailureAttribute(request, e);
return true;
}
//是ajax請求,咱們返回json給瀏覽器
String message = e.getClass().getSimpleName();
if ("IncorrectCredentialsException".equals(message)) {
WebUtils.printCNJSON("{\"message\":\"密碼錯誤\"}", httpServletResponse);
} else if ("UnknownAccountException".equals(message)) {
WebUtils.printCNJSON("{\"message\":\"帳號不存在\"}", httpServletResponse);
} else if ("captchaCodeError".equals(message)) {
WebUtils.printCNJSON("{\"message\":\"驗證碼錯誤\"}", httpServletResponse);
} else {
WebUtils.printCNJSON("{\"message\":\"未知錯誤\"}", httpServletResponse);
}
return false;
}
/** * 判斷ajax請求 * * @param request * @return */
boolean isAjax(HttpServletRequest request) {
return (request.getHeader("X-Requested-With") != null && "XMLHttpRequest".equals(request.getHeader("X-Requested-With").toString()));
}
}
複製代碼
值得一提的是:手動返回JSON格式數據、要是標準的JSON格式。不然會出錯
參考資料:
login.do代碼:
@RequestMapping("/login.do")
public String login(HttpServletRequest request, HttpServletResponse response) throws Exception {
/** * 若是在shiro配置文件中配置了authc的話,那麼所點擊的url就須要認證了才能夠訪問 * a:若是url是登錄請求地址(user/login.do),不是post請求的話,流程是不會去Realm中的。那麼會返回到該方法中,也就是會返回登錄頁面 * b:若是url是登錄頁面地址,是post請求的話,那麼去realm中對比,若是成功了那麼跳轉到在表單過濾器中配置的url中 * * c:若是url不是登錄頁面地址,那麼表單過濾器會重定向到此方法中,該方法返回登錄頁面地址。並記住是哪一個url被攔截住了 * d:用戶填寫完表單以後,會進入b環節,此時登錄成功後跳轉的頁面是c環節記住的url。 */
//若是登錄失敗從request中獲取認證異常信息,shiroLoginFailure就是shiro異常類的全限定名
String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
//根據shiro返回的異常類路徑判斷,拋出指定異常信息
if (exceptionClassName != null) {
if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
throw new UserException("帳號不存在");
} else if (IncorrectCredentialsException.class.getName().equals(
exceptionClassName)) {
throw new UserException("密碼錯誤了");
} else if ("captchaCodeError".equals(exceptionClassName)) {
throw new UserException("驗證碼錯誤了");
} else {
throw new Exception();//最終在異常處理器生成未知錯誤
}
}
return "redirect:/goURL/user/toLogin.do";
}
複製代碼
若是您以爲這篇文章幫助到了您,能夠給做者一點鼓勵