avatar

05.CURD实验之登录功能

一、登录功能

  1. Controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @Controller
    public class LoginController {
    @RequestMapping("/login")
    public String login(HttpServletRequest request, Model m){
    //获取参数
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    //这里不查数据库。就直接伪造数据。只有当用户名是admin,密码是admin才成功
    if("admin".equals(username) && "admin".equals(password)){
    Map<String,Object> map= new HashMap<>();
    map.put("username",username);
    map.put("password",password);
    //保存session。跳转到后台
    request.getSession().setAttribute("user",map);
    return "redirect:/dashboard.html";
    }
    m.addAttribute("msg","账号或密码错误!");
    return "/index";
    }
    }
  2. 页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <title>Signin Template for Bootstrap</title>
    <!-- Bootstrap core CSS -->
    <link href="asserts/css/bootstrap.min.css" rel="stylesheet">
    <!-- Custom styles for this template -->
    <link href="asserts/css/signin.css" rel="stylesheet">
    </head>

    <body class="text-center">
    <form class="form-signin" th:action="@{/login}" method="post">
    <img class="mb-4" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72">
    <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.welcome}">Please sign in</h1>
    <!-- th:if 只有满足了表达式中的条件才会显示 -->
    <p th:text="${msg}" th:if="${msg}!=null and not ${#strings.isEmpty(msg)}" style="color: red"></p>
    <label class="sr-only" th:text="#{login.username}">Username</label>
    <input type="text" name="username" class="form-control" th:placeholder="#{login.username}" required="" >
    <label class="sr-only" th:text="#{login.password}">Password</label>
    <input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required="">
    <div class="checkbox mb-3">
    <label>
    <input type="checkbox" value="remember-me"> [[#{login.remember}]]
    </label>
    </div>
    <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.loginBtn}">Sign in</button>
    <p class="mt-5 mb-3 text-muted">© 2017-2018</p>
    <a class="btn btn-sm">中文</a>
    <a class="btn btn-sm">English</a>
    </form>

    </body>

    </html>
  3. 遇到的问题

    1. 发现在Controller中登录成功后,重定向到dashboard.html页面会报404错误。如图

      • 原因:因为redirect和forword请求不会触发Thymeleaf模版引擎去渲染。而页面又在templates目录下,默认的模版解析器不会扫描templates目录。所以出现404错误。Thymeleaf视图解析器ThymeleafViewResolver代码片段如下:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        @Override
        protected View createView(final String viewName, final Locale locale) throws Exception {
        // First possible call to check "viewNames": before processing redirects and forwards
        if (!this.alwaysProcessRedirectAndForward && !canHandle(viewName, locale)) {
        vrlogger.trace("[THYMELEAF] View \"{}\" cannot be handled by ThymeleafViewResolver. Passing on to the next resolver in the chain.", viewName);
        return null;
        }
        // Process redirects (HTTP redirects)
        if (viewName.startsWith(REDIRECT_URL_PREFIX)) { //如果Controller返回值是redirect开头。则执行下面
        vrlogger.trace("[THYMELEAF] View \"{}\" is a redirect, and will not be handled directly by ThymeleafViewResolver.", viewName); //日志的意识,大概是该视图是redirect,ThymeleafViewResolver将不处理
        final String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length(), viewName.length());
        final RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());//RedirectView里面实际上是调用response.sendRedirect方法。可参见renderMergedOutputModel()
        return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);
        }
        // Process forwards (to JSP resources)
        if (viewName.startsWith(FORWARD_URL_PREFIX)) { //如果Controller返回值是forword开头,则执行下面
        // The "forward:" prefix will actually create a Servlet/JSP view, and that's precisely its aim per the Spring
        // documentation. See http://docs.spring.io/spring-framework/docs/4.2.4.RELEASE/spring-framework-reference/html/mvc.html#mvc-redirecting-forward-prefix
        //上面日志的大概意识是:转发实际上是创建了一个jsp视图。所以下面直接new一个InternalResourceView去处理,底层是调用request转发
        vrlogger.trace("[THYMELEAF] View \"{}\" is a forward, and will not be handled directly by ThymeleafViewResolver.", viewName);
        final String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length(), viewName.length());
        return new InternalResourceView(forwardUrl);
        }
        // Second possible call to check "viewNames": after processing redirects and forwards
        if (this.alwaysProcessRedirectAndForward && !canHandle(viewName, locale)) {
        vrlogger.trace("[THYMELEAF] View \"{}\" cannot be handled by ThymeleafViewResolver. Passing on to the next resolver in the chain.", viewName);
        return null;
        }
        vrlogger.trace("[THYMELEAF] View {} will be handled by ThymeleafViewResolver and a " +
        "{} instance will be created for it", viewName, getViewClass().getSimpleName());
        return loadView(viewName, locale);
        }
      • 解决方法:

        • 在application.yml中把templates目录加到spring.resources.static-locations中(不推荐。这样页面就会当静态页面处理。不会所有人都可以通过地址栏访问,而且也不会渲染。)

        • 在配置类中。添加视图映射。把/dashboard.html 映射到视图dashboard 即可。代码如下:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          @Configuration
          public class Config implements WebMvcConfigurer {
          ………………//省略前面若干代码
          //添加资源映射
          @Override
          public void addViewControllers(ViewControllerRegistry registry) {
          ………………//省略前面映射主页的代码
          //添加/dashboard.html的映射
          registry.addViewController("/dashboard.html").setViewName("dashboard");
          }
          }

二、拦截器

  1. 登录拦截器可以直接写在配置类中。因为配置类实现了WebMvcConfigurer 类。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    @Configuration
    public class Config implements WebMvcConfigurer {
    //添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    //注册登录拦截器,并设置例外请求
    registry.addInterceptor(new HandlerInterceptor() {
    @Override
    //在方法执行前拦截
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    //获取Session
    HttpSession session = request.getSession();
    //获取属性
    Map<String,Object> user = (Map<String, Object>) session.getAttribute("user");
    //判断属性是否为空
    if(user==null){
    //等于空则说明未登录。返回false,返回登录页
    response.sendRedirect("/index.html");
    return false;
    }
    //不为空,则说明已登录,放行
    return true;
    }
    //设置排除,因为在SpringBoot2.x+版本中,静态资源也会被拦截。所以需要加上排除
    }).excludePathPatterns("/login","/","index.html","login.html","index","/static/**");

    }
    }
  2. 在SpringBoot2.x+版本中,静态资源也会被拦截器拦截。所以我们需要在设置排除时加上静态资源路径。原因如下:

    • spring boot 1.5.x依赖的spring 4.3.x版本和spring boot 2.x依赖的spring 5.x版本对拦截器的初始化做的操作有区别。具体的源码在WebMvcConfigurationSupport 中。

      • spring boot 1.5.x 源码:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        /**
        * Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped
        * resource handlers. To configure resource handling, override
        * {@link #addResourceHandlers}.
        */
        @Bean
        public HandlerMapping resourceHandlerMapping() {
        ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
        this.servletContext, mvcContentNegotiationManager());
        addResourceHandlers(registry);

        AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
        if (handlerMapping != null) {
        handlerMapping.setPathMatcher(mvcPathMatcher());
        handlerMapping.setUrlPathHelper(mvcUrlPathHelper());
        // 此处固定添加了一个Interceptor
        handlerMapping.setInterceptors(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));
        handlerMapping.setCorsConfigurations(getCorsConfigurations());
        }
        else {
        handlerMapping = new EmptyHandlerMapping();
        }
        return handlerMapping;
        }
      • spring boot 2.x源码:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        /**
        * Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped
        * resource handlers. To configure resource handling, override
        * {@link #addResourceHandlers}.
        */
        @Bean
        public HandlerMapping resourceHandlerMapping() {
        Assert.state(this.applicationContext != null, "No ApplicationContext set");
        Assert.state(this.servletContext != null, "No ServletContext set");

        ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
        this.servletContext, mvcContentNegotiationManager(), mvcUrlPathHelper());
        addResourceHandlers(registry);

        AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
        if (handlerMapping != null) {
        handlerMapping.setPathMatcher(mvcPathMatcher());
        handlerMapping.setUrlPathHelper(mvcUrlPathHelper());
        // 此处是将所有的HandlerInterceptor都添加了(包含自定义的HandlerInterceptor)
        handlerMapping.setInterceptors(getInterceptors());
        handlerMapping.setCorsConfigurations(getCorsConfigurations());
        }
        else {
        handlerMapping = new EmptyHandlerMapping();
        }
        return handlerMapping;
        }

        /**
    • Provide access to the shared handler interceptors used to configure
      • {@link HandlerMapping} instances with. This method cannot be overridden,
      • use {@link #addInterceptors(InterceptorRegistry)} instead.
        */
        protected final Object[] getInterceptors() {
        if (this.interceptors == null) {
        InterceptorRegistry registry = new InterceptorRegistry();
        // 此处传入新new的registry对象,在配置类当中设置自定义的HandlerInterceptor后即可获取到
        addInterceptors(registry);
        registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService()));
        registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));
        this.interceptors = registry.getInterceptors();
        }
        return this.interceptors.toArray();
        }
      1
       

评论