從零開始寫項目第三篇【登錄註冊模塊】


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參考教程:

blog.csdn.net/csdn_yudong…

使用時要注意的是:particles div中包含的內容須要絕對定位。

註冊的時候須要作前臺校驗的。原本是想本身寫正則表達式作校驗的,後來發現有BootstrapValidation這樣一個組件。因而就去下載使用了。

boostrapValidation參考教程:

www.cnblogs.com/huangcong/p…

blog.csdn.net/nazhidao/ar…

bootstrapValidation有一個坑,**submitHandler提交方法只有0.45版本纔有效(親測)。**後來在尋找版本的時候發現了一個很是有用的網站:可以查詢到多個版本,十分好用。

www.bootcdn.cn/

通過一頓整改,個人頁面和驗證效果以下:

註冊後臺

作了前臺的校驗是不夠的。由於有可能別人是知道咱們註冊的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>

複製代碼

參考資料:

www.cnblogs.com/liukemng/p/…

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文件就行了。

資料以下:

mishengqiang.com/sweetalert/

效果以下:

搞了那麼久,就剩下入數據庫了。

/** * 先對數據進行校驗,再註冊 * * @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以前是沒有接觸過的。因而就去找了幾篇資料:

www.cnblogs.com/leechenxian…

www.cnblogs.com/codeplus/ar…

我是使用qq郵箱去發送郵件的:要去qq郵箱申請受權碼才能發送

在看上邊資料的時候,發現郵件其實用freemarker來作模版會很不錯(由於郵箱的只有少部份內容是變的),因而又去找freemarker與spring整合的資料:

blog.csdn.net/lpjishu/art…

blog.csdn.net/u013111003/…

www.jb51.net/article/432…

上訴的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路徑的相對路徑、絕對路徑、項目路徑已有些混亂了。後來經過一篇資料又好好地整理一下:

www.cnblogs.com/franson-201…

  • 個人總結:凡是以「/"開頭的都表明絕對路徑,在Controller裏邊"/"前面表明着http://localhost:8080/項目名
  • 不管經過Servlet、仍是API獲取獲得都是在本機上的路徑。

不少時候咱們的項目路徑在不一樣機器上是不同的。所以要作到更好的通用性,能夠將其在配置文件中配置起來。

提供一個工具類提取它就好了:

/** * 根據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「**才知道網上也有人遇到我這種狀況。

參考資料:

blog.csdn.net/haiyang0735…

my.oschina.net/WMSstudio/b…

大體的思路就知道了:重寫自定義表單過濾器的方法,判斷是否爲ajax請求來進行處理

期間找了不少相關的資料,每一個人的實現都良莠不齊。表單過濾器方法中的retrun truereturn false也把我搞得一頭霧水。最後,迴歸到搜索關鍵字「Shiro認證流程「,找到了一篇解決我問題的博文:

www.cnblogs.com/leechenxian…

通過上面的資料和閱讀了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格式。不然會出錯

參考資料:

www.cnblogs.com/54td/p/6074…

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";
    }
複製代碼

若是您以爲這篇文章幫助到了您,能夠給做者一點鼓勵

相關文章
相關標籤/搜索