Dreamer-1

Java结合SpringBoot拦截器实现简单的登录认证模块

之前在做项目时需要实现一个简单的登录认证的功能,就寻思着使用Spring Boot的拦截器来实现,在此记录一下我的整个实现过程,源码见文章底部。

1. 环境搭建

IntelliJ IDEA + Java8 + Spring Boot + Tomcat
我将之前项目中的登录模块抽离出来,单独放在了一个新建的Spring Boot项目中;
整个项目的主要结构如下:
项目结构

参考资料:使用IDEA创建Spring Boot项目

2. 代码详解

2.1 前端代码

之前项目里别的小伙伴已经写好了一个简单的登录框样式表(login.css)和一些image图,我这里就顺手拿来用了,希望哪天你见了眼熟别拍我…
login.vm代码:
注意前端传递给后端Controller的password值并不是用户实际输入的密码!
实际传递的是用户名 + 密码(统一小写)组合的字符串的md5信息值;
这样在前后台数据传递及后台数据保存时传递和保存的都不是用户的真实密码值,可以一定程度提升安全性及规避某些风险;

更多资料可参考:Web前端密码加密是否有意义

<html>
<head>
    <title>系统登录</title>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <meta name="author" content="Dreamer-1">
    <meta name="renderer" content="webkit" />

    <link href="/css/login/login.css" rel="stylesheet">
    <script type="text/javascript" src="/js/login/jquery/jquery.min.js?v=20170207"></script>
    <script type="text/javascript" src="/js/login/md5/md5.js"></script>
</head>

<body>
    <form name="form1" method="post" action="/login" id="form1" onsubmit="return checkLogin();">
        <div id="main">
        <div class="wrapper">
            <div class="login-hd"></div>
            <div class="login-body">
                <div class="logo">
                    <span class="icon-logo"></span>
                </div>
                <div class="box">
                    <div class="login-item">
                        <span class="icon-user"></span>
                        <input name="username" type="text" id="username" class="login-input" tabindex="1" maxlength="50" placeholder="请输入用户名" />

                    </div>
                    <div class="login-item mt35">
                        <span class="icon-pwd"></span>
                        <input type="password" id="password"  class="login-input" tabindex="2" maxlength="32" placeholder="请输入密码"/>
                        <input type="hidden" id="hidePwd" name="password">
                    </div>
                    <div class="login-forget" style="visibility:hidden">
                        <a href="#">忘记密码</a>
                    </div>

                    <input type="submit" name="Logon" value="登录" id="Logon" tabindex="3" class="login-btn" />
                    <div class="login-bottom">
                        <div class="msg"  style="display:none;" >
                            <span class="icon-err"></span>
                            <span id="message"></span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        </div>
    </form>

    <script type="text/javascript">
        // onsubmit值为true时,提交表单,否则显示错误信息
        // 生成用户名+密码组合的md5值,并设置传给后端的密码为该md5值
        function checkLogin() {
        var name = $("#username").val().toLowerCase();
        var pwd = $("#password").val().toLowerCase();
        if(name.trim()=="" || pwd.trim()=="") {
            $("#message").text("请输入用户名和密码");
            $(\'.msg\').show();
            return false;
        }else {
            $(\'.msg\').hide();
        }

        var md5info = name + pwd;
        $(\'#hidePwd\').val(md5(md5info));
        //$("#password").val();
        return true;
    }
    </script>
</body>
</html>

welcome.vm代码
登录成功后显示welcome.vm页的内容,这个页面很简单:

<h1 align="center">登录成功!!!</h1>

<br>
<h3><a href="/loginout"><font color="red">退出登录</font></a></h3>

2.2 后端代码

后端代码相较于前端要复杂一些,让我们来一一拆解;

2.2.1 程序入口

ManApplication.java是整个程序的主入口,因为其上打了@SpringBootApplication的注解;
注意:Spring Boot项目在tomcat上部署运行时,ManApplication需要继承SpringBootServletInitializer
ManApplication.java代码:

/**
 * @SpringBootApplication 注解标明该类是本程序的入口
 */
@SpringBootApplication
public class ManApplication extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(ManApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(ManApplication.class, args);
    }
}
2.2.2 Controller

IndexViewController.java类里就是简单的URL映射;

/**
 * Created with logindemo.
 * Author: dreamer-1
 * Email: zhong--lei@outllok.com
 * Date: 2018/5/13
 * Time: 下午2:58
 * Description:
 */
@Controller
public class IndexViewController {
    /**
     * 登录
     * @return
     */
    @GetMapping("/")
    public String index() {
        return "login";
    }

    /**
     * 欢迎页
     * @return
     */
    @GetMapping("/welcome")
    public String welcome() {
        return "welcome";
    }
}

LoginViewController.java类接收前端传过来的username和password,进行简单的校验和重定向;
此处为了简单就只设置了一个正确的账号和密码用于校验,你后续使用时可以结合自己的实际需求来扩充整个校验逻辑(比如通过专门的表来存储用户登录信息等);
用户名和密码校验通过后会在当前会话的session中放入一个登录标识,以表示当前用户已经登录;在退出登录或会话超时时销毁该标识;

/**
 * Created with logindemo.
 * Author: dreamer-1
 * Email: zhong--lei@outllok.com
 * Date: 2018/5/13
 * Time: 下午2:49
 * Description:
 */
@Controller
public class LoginViewController {

    // 预先设置好的正确的用户名和密码,用于登录验证
    private String rightUserName = "admin";
    private String rightPassword = "admin";

    /**
     * 登录校验
     *
     * @param request
     * @return
     */
    @RequestMapping("/login")
    public String login(HttpServletRequest request) {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        if (null == username || null == password) {
            return "redirect:/";
        }

        // 前端传回的密码实际为用户输入的:用户名(小写)+ 密码(小写)组合的字符串生成的md5值
        // 此处先通过后台保存的正确的用户名和密码计算出正确的md5值,然后和前端传回来的作比较
        String md5info = rightUserName.toLowerCase() + rightPassword.toLowerCase();
        String realPassword = DigestUtils.md5DigestAsHex(md5info.getBytes());
        if (!password.equals(realPassword)) {
            return "redirect:/";
        }

        // 校验通过时,在session里放入一个标识
        // 后续通过session里是否存在该标识来判断用户是否登录
        request.getSession().setAttribute("loginName", "admin");
        return "redirect:/welcome";
    }

    /**
     * 注销登录
     *
     * @param request
     * @return
     */
    @RequestMapping("/loginout")
    public String loginOut(HttpServletRequest request) {
        request.getSession().invalidate();
        return "redirect:/";
    }

}
2.2.3 Interceptor

LoginInterceptor.java是整个登录认证模块中的核心类之一,它实现了HandlerInterceptor类,由它来拦截并过滤到来的每一个请求;它的三个方法能分别作用于每个请求的不同生命周期,你可以根据自己的需要来加入相应的处理逻辑;

/**
 * Created with logindemo.
 * Author: dreamer-1
 * Email: zhong--lei@outllok.com
 * Date: 2018/5/13
 * Time: 下午2:58
 * Description:
 */
public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 在请求被处理之前调用
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 检查每个到来的请求对应的session域中是否有登录标识
        Object loginName = request.getSession().getAttribute("loginName");
        if (null == loginName || !(loginName instanceof String)) {
            // 未登录,重定向到登录页
            response.sendRedirect("/");
            return false;
        }
        String userName = (String) loginName;
        System.out.println("当前用户已登录,登录的用户名为: " + userName);
        return true;
    }

    /**
     * 在请求被处理后,视图渲染之前调用
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    /**
     * 在整个请求结束后调用
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}
2.2.4 Configuration

LoginConfiguration.java是另一个核心类之一,它实现了WebMvcConfigurer类,负责注册并生效我们自己定义的拦截器配置;
在这里要注意定义好拦截路径和排除拦截的路径;

WebMvcConfigurerAdapter其实还可以做很多其他的事,包括添加自定义的视图控制器等等,详见这里

/**
 * Created with logindemo.
 * Author: dreamer-1
 * Email: zhong--lei@outllok.com
 * Date: 2018/5/13
 * Time: 下午2:58
 * Description:
 */
@Configuration
public class LoginConfiguration implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册拦截器
        LoginInterceptor loginInterceptor = new LoginInterceptor();
        InterceptorRegistration loginRegistry = registry.addInterceptor(loginInterceptor);
        // 拦截路径
        loginRegistry.addPathPatterns("/**");
        // 排除路径
        loginRegistry.excludePathPatterns("/");
        loginRegistry.excludePathPatterns("/login");
        loginRegistry.excludePathPatterns("/loginout");
        // 排除资源请求
        loginRegistry.excludePathPatterns("/css/login/*.css");
        loginRegistry.excludePathPatterns("/js/login/**/*.js");
        loginRegistry.excludePathPatterns("/image/login/*.png");
    }
}

3. 踩坑与填坑

3.1 多次重定向与Circle view path错误

刚开始程序部署至tomcat里运行时,理所当然的出现了意想不到的情况,详情如下:
启动时localhost:8080显示:
重定向次数过多

后台一直报错:
template might not exist

通过断点调试,发现启动后不停地进入IndexViewController中的“/”这个URL映射里;
手动指定访问路径为 localhost:8080/welcome 时后台报错:
cycle-view-path

解决办法:

  1. 在pom文件中导入thymeleaf依赖:
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
  1. 在application.properties里添加如下配置:
	spring.thymeleaf.prefix=classpath:/templates/
	spring.thymeleaf.suffix=.vm

我猜想可能是我的view文件都以.vm结尾,thymeleaf默认找的是.html结尾的视图,所以一直找不到;
有知道的大神可以在下面留言详细讲解一下 _

4. 大功告成

4.1 运行截图

未登录情况下访问 localhost:8080/welcome 等非登录页面时会自动跳转到登录页面:
运行截图

4.2 源码下载

戳这里下载我哟

分类:

技术点:

相关文章: