从零开始写项目第二篇【登陆注册、聊天、收藏夹模块】

 
 

登陆模块目标

我要将其弄成类似的登陆,功能是要比较完善的。

640?wx_fmt=png

本来我是想做一步写一步的,但是发现这样文章就会太乱,因为要改的地方太多了。前面写过的,后边就被修改了。这样看起来太混乱了。因此我现在是写完代码才来补这篇记录的。尽可能把当时的思路记录下来,并且捋一捋看一下有没有不足的地方。

编写注册模块过程

我的登陆注册模块主要是使用邮箱,这部分之前是没有了解过的。因此去找了一点资料。想完成使用邮箱来激活用户的思路可看下面:

http://blog.csdn.net/u013863751/article/details/45600651

根据这个思路,我就创建数据库表了,数据库表的设计如下:

640?wx_fmt=png

设计完数据库表之后就去写前台页面了。因为登陆注册这部分的Dao层并不难,就几个简单的方法。

注册页面和校验

前台我使用的是Bootstrap为主,登陆注册模块是使用bootstrap官网提供的模版。

640?wx_fmt=png

导航栏是使用之前看见过别人博客上的导航条:

http://blog.csdn.net/iamcgt/article/details/72863303

有了导航条和bootstrap官网提供的登陆注册模版了,背景现在太过单薄了。我想到了一个开源的项目:https://github.com/VincentGarreau/particles.js。于是下载下来改了一下。我的首页样式就有了。

particles.js参考教程:

http://blog.csdn.net/csdn_yudong/article/details/53128570

使用时要注意的是:particles  div中包含的内容需要绝对定位。

640?wx_fmt=png

注册的时候需要做前台校验的。本来是想自己写正则表达式做校验的,后来发现有BootstrapValidation这样一个组件。于是就去下载使用了。

boostrapValidation参考教程:

http://www.cnblogs.com/huangcong/p/5335376.html

http://blog.csdn.net/nazhidao/article/details/51542508

bootstrapValidation有一个坑,submitHandler提交方法只有0.45版本才有效(亲测)。后来在寻找版本的时候发现了一个非常有用的网站:能够查询到多个版本,十分好用。

http://www.bootcdn.cn/

经过一顿整改,我的页面和验证效果如下:

640?wx_fmt=png
640?wx_fmt=png

注册后台

做了前台的校验是不够的。因为有可能别人是知道我们注册的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>
       <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>

参考资料:

http://www.cnblogs.com/liukemng/p/3738055.html

https://zhongfucheng.bitcron.com/post/springmvc/springmvcdi-liu-pian-xiao-yan-tong-yi-chu-li-yi-chang

在写配置文件的时候虽然idea提示是红色的,但是无妨,还是可以或获取得到的。对其忽略就行

640?wx_fmt=png

那么我们的后台校验也已经完成了。

还有需要做校验的就是,如果用户已经注册过了,我们就不能让它再注册了。为了达到更好的用户体检,在提交表单前使用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("系统错误!");                        }                    });                }
               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文件就好了。

资料如下:

http://mishengqiang.com/sweetalert/

效果如下:

640?wx_fmt=png

搞了那么久,就剩下入数据库了。

    /**     * 先对数据进行校验,再注册     *     * @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";    }
   @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>
 <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之前是没有接触过的。于是就去找了几篇资料:

http://www.cnblogs.com/leechenxiang/p/6367432.html

http://www.cnblogs.com/codeplus/archive/2011/11/03/2232893.html

我是使用qq邮箱去发送邮件的:要去qq邮箱申请授权码才能发送

640?wx_fmt=png

在看上边资料的时候,发现邮件其实用freemarker来做模版会很不错(因为邮箱的只有少部分内容是变的),于是又去找freemarker与spring整合的资料:

http://blog.csdn.net/lpjishu/article/details/51902024

http://blog.csdn.net/u013111003/article/details/52118514

http://www.jb51.net/article/43200.htm

上诉的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>
       <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格式的数据就行了。

/** * 邮件服务类,提供发送邮件的功能 */@Servicepublic 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;    }}
@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>
   <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路径的相对路径、绝对路径、项目路径已有些混乱了。后来通过一篇资料又好好地整理一下:

http://www.cnblogs.com/franson-2016/p/5728280.html

很多时候我们的项目路径在不同机器上是不一样的。因此要做到更好的通用性,可以将其在配置文件中配置起来。

640?wx_fmt=png

提供一个工具类提取它就行了:

/** * 根据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);    }}
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);
   }
}

扯了这么一大堆,我们的邮件已经能够发出去了

640?wx_fmt=png

url链接使用了base64进行编码了,其实没什么,就是为了装个逼而已..

maven坐标:

        <!--base64编码解码-->        <dependency>            <groupId>commons-codec</groupId>            <artifactId>commons-codec</artifactId>            <version>1.6</version>        </dependency>
       <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;    }}
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";    }
   @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类:

640?wx_fmt=png

来源github项目:https://github.com/baichengzhou/SpringMVC-Mybatis-Shiro-redis-0.2

登陆后台

我引入了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>
       <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博文:

https://zhongfucheng.bitcron.com/category/shiro

想要在shiro认证之前能够校验验证码的话,那么就需要我们去自定义表单过滤器了。

/** * 自定义一个表单过滤器的目的就是认证流程由自己控制 */public class UserFormAuthenticationFilter extends FormAuthenticationFilter {}
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;    }
   @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("系统错误");                }            })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";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();    }}
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“才知道网上也有人遇到我这种情况。

参考资料:

http://blog.csdn.net/haiyang0735/article/details/51278387

https://my.oschina.net/WMSstudio/blog/162594

大致的思路就知道了:重写自定义表单过滤器的方法,判断是否为ajax请求来进行处理

期间找了很多相关的资料,每个人的实现都参差不齐。表单过滤器方法中的retrun truereturn false也把我搞得一头雾水。最后,回归到搜索关键字“Shiro认证流程“,找到了一篇解决我问题的博文:

http://www.cnblogs.com/leechenxiang/p/7070229.html

经过上面的资料和阅读了Shiro相关的源码,我基本能知道shiro认证流程了,下面是我画的一张流程图:

640?wx_fmt=png

根据流程可以判断在验证码失败时如果是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()));    }}

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格式。否则会出错

参考资料:

http://www.cnblogs.com/54td/p/6074456.html

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";    }"/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";
   }

忘记密码

对于忘记密码这个功能并不难,就跳转到忘记密码页面,让用户输入邮箱,去邮箱所给的超链接修改密码就行了。

640?wx_fmt=png

记住我功能

本来我是想在登陆的时候勾选“记住我”,那么下次访问的时候,就可以直接访问了(因为我设置了一些链接是需要认证后才能访问的)。

原本是想使用Shiro的rememberMe这个功能的。发现它其实并没有为我们自动实现登陆

具体的参考链接如下:

http://blog.csdn.net/nsrainbow/article/details/36945267

查了挺多资料的,发现Shiro的rememberMe这个功能并不好用,但我的系统是基于Shiro来进行开发的。

根据源码的文档说明:

public interface RememberMeAuthenticationToken extends AuthenticationToken {    /**     * Returns {@code true} if the submitting user wishes their identity (principal(s)) to be remembered     * across sessions, {@code false} otherwise.     *     * @return {@code true} if the submitting user wishes their identity (principal(s)) to be remembered     *         across sessions, {@code false} otherwise.     */    boolean isRememberMe();}interface RememberMeAuthenticationToken extends AuthenticationToken {

   /**
    * Returns {@code true} if the submitting user wishes their identity (principal(s)) to be remembered
    * across sessions, {@code false} otherwise.
    *
    * @return {@code true} if the submitting user wishes their identity (principal(s)) to be remembered
    *         across sessions, {@code false} otherwise.
    */

   boolean isRememberMe();

}

只有在当前用户的session(Shiro的Session)存储了已认证的用户,那么才承认当前的用户是已登陆的。

我也想要实现rememberMe这个功能,那么只能手动去弄了

我是通过拦截器来实现的:

public class LoginInterceptor implements HandlerInterceptor {    @Autowired    private UserService userService;    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {        Subject currentUser = SecurityUtils.getSubject();        //如果 isAuthenticated 为 false 证明不是登录过的,同时 isRememberd 为true 证明是没登陆直接通过记住我功能进来的        if (!currentUser.isAuthenticated() && currentUser.isRemembered()) {            ActiveUser activeUser = (ActiveUser) SecurityUtils.getSubject().getPrincipal();            //获取session看看是不是空的            Session session = currentUser.getSession();            if (session.getAttribute("currentUser") == null) {                User user = userService.validateUserExist(activeUser.getUsercEmail());                UsernamePasswordToken token = new UsernamePasswordToken(user.getUserEmail(), activeUser.getPassword(), currentUser.isRemembered());                //把当前用户放入session                currentUser.login(token);                session.setAttribute("currentUser",user);                //设置会话的过期时间--ms,默认是30分钟,设置负数表示永不过期                session.setTimeout(-1000l);                //这是httpSession、用户页面获取数据的。                httpServletRequest.getSession().setAttribute("activeUser", activeUser);            }        }        return true;    }    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {    }    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {    }}class LoginInterceptor implements HandlerInterceptor {
   @Autowired
   private UserService userService;

   public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {



       Subject currentUser = SecurityUtils.getSubject();

       //如果 isAuthenticated 为 false 证明不是登录过的,同时 isRememberd 为true 证明是没登陆直接通过记住我功能进来的
       if (!currentUser.isAuthenticated() && currentUser.isRemembered()) {

           ActiveUser activeUser = (ActiveUser) SecurityUtils.getSubject().getPrincipal();


           //获取session看看是不是空的
           Session session = currentUser.getSession();
           if (session.getAttribute("currentUser") == null) {

               User user = userService.validateUserExist(activeUser.getUsercEmail());

               UsernamePasswordToken token = new UsernamePasswordToken(user.getUserEmail(), activeUser.getPassword(), currentUser.isRemembered());

               //把当前用户放入session
               currentUser.login(token);

               session.setAttribute("currentUser",user);
               //设置会话的过期时间--ms,默认是30分钟,设置负数表示永不过期
               session.setTimeout(-1000l);

               //这是httpSession、用户页面获取数据的。
               httpServletRequest.getSession().setAttribute("activeUser", activeUser);

           }
       }

       return true;
   }

   public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

   }

   public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

   }
}

那么就可以实现当用户即使关闭了浏览器之后,再次打开浏览器访问我们的页面还是登陆状态!

对登陆注册优化

最开始在页面使用路径都是自己手写:http://localhost:8080/zhongfucheng/这样子的。这样并不通用。想要做得通用的话,那么就要使用变量了。而我又不想使用JSP页面。于是我的项目统一使用freemarker

跳转到某个url的逻辑其实是一样的,没必要为每个跳转都写一个方法。于是我将其抽取出来:

    /**     * 通用的页面跳转     *     * @param folder 模块     * @param file   具体文件     * @return     */    @RequestMapping("/goURL/{folder}/{file}.do")    public String goURL(@PathVariable("folder") String folder, @PathVariable("file") String file) {        return "/" + folder + "/" + file + ".ftl";    }
   @RequestMapping("/goURL/{folder}/{file}.do")
   public String goURL(@PathVariable("folder") String folder, @PathVariable("file") String file) {

       return "/" + folder + "/" + file + ".ftl";
   }

对于首页而言我们可以使用监听器来对其进行初始化

public class ProjectListener implements ServletContextListener {    /**     * 当项目启动的时候,我们就根据模版生成我们的index页面     *     * @param servletContextEvent     */    public void contextInitialized(ServletContextEvent servletContextEvent) {        System.out.println(servletContextEvent.getServletContext().getRealPath("/"));        try {            FreeMarkerUtils markerUtils = new FreeMarkerUtils();            Map map = new HashMap();            map.put("path", ReadPropertiesUtil.readProp("projectPath"));            markerUtils.ouputFile("index.ftl", "index.html", map);        } catch (Exception e) {            e.printStackTrace();        }    }    public void contextDestroyed(ServletContextEvent servletContextEvent) {    }}class ProjectListener implements ServletContextListener {



   /**
    * 当项目启动的时候,我们就根据模版生成我们的index页面
    *
    * @param servletContextEvent
    */

   public void contextInitialized(ServletContextEvent servletContextEvent) {

       System.out.println(servletContextEvent.getServletContext().getRealPath("/"));
       try {
           FreeMarkerUtils markerUtils = new FreeMarkerUtils();
           Map map = new HashMap();
           map.put("path", ReadPropertiesUtil.readProp("projectPath"));
           markerUtils.ouputFile("index.ftl", "index.html", map);
       } catch (Exception e) {
           e.printStackTrace();
       }

   }

   public void contextDestroyed(ServletContextEvent servletContextEvent) {

   }
}

在JavaScript中可能也需要用到http://localhost:8080/zhongfucheng/路径,我们可以使用JavaScript来获取项目的该路径,而不用写死:

    <!--使用JS获取项目根路径-->    <script>        var path = "";        $(function () {            var strFullPath = window.document.location.href;            var strPath = window.document.location.pathname;            var pos = strFullPath.indexOf(strPath);            var prePath = strFullPath.substring(0, pos);            var postPath = strPath.substring(0, strPath.substr(1).indexOf('/') + 1);            path = prePath + postPath;        });    </script><script>
       var path = "";
       $(function () {
           var strFullPath = window.document.location.href;
           var strPath = window.document.location.pathname;
           var pos = strFullPath.indexOf(strPath);
           var prePath = strFullPath.substring(0, pos);
           var postPath = strPath.substring(0, strPath.substr(1).indexOf('/') + 1);
           path = prePath + postPath;
       });
   
</script>

比如我们在获取用户信息的时候就很方便了。

640?wx_fmt=png

在代码会重复的情况下封装一些常用的Utils,或者使用别人写好的Utils

640?wx_fmt=png

如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同学,可以关注微信公众号:Java3y


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值