SpringBoot Security 整合thymeleaf模

作者: 程就人生 | 来源:发表于2019-06-19 21:53 被阅读16次

    使用SpringBoot Security进行登录验证,可以结合具体的业务需求来使用。在
    SpringBoot Security前后端分离,登录退出等返回json
    一文中,描述了前后端分离的情况下,如何进行登录验证和提示错误信息的。现在针对自定义的登录页面,能够精确地提示错误信息,做一个简单的演示demo。

    本文使用的SpringBoot版本是2.1.4.RELEASE,下面直接进入使用阶段。

    第一步,在pom.xml中引入架包
    <!-- security架包 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
    

    加上这个架包,重启项目后,整个项目就配置了登录拦截和验证。

    第二步,重启项目,会在控制台下看到自动生成的登录密码,默认的用户名是admin

    图-1

    第三步,打开浏览器窗口,对登录页面进行探究

    图-2

    不输入用户名和密码,直接点击登录时,会有提示信息,输入框的颜色还会变红。查看源码,可以发现,架包默认的登录页面提交方式为表单提交,method为post,并且默认是开启csrf的,在表单里自动生成了一个隐藏域,防止跨域提交,确保请求的安全性。


    图-2

    输入错误的用户名或密码,可以看到页面进行了跳转,跳转后的页面又回到了登录页,只是url地址后面多了一个参数,页面提示错误信息。


    图-3

    从页面源码,我们可以获得以下几个方面的信息:

    1. 自带的登录页面使用post方式提交;
    2. 用户名密码错误时,页面会进行重定向,重定向到登录页面,并展示错误信息。

    如果页面是我们自己自定义的,如果要使用默认的过滤器获取登录信息,则必须使用post方式进行提交,如果使用ajax json的方式进行提交,则获取不到参数。

    接下来自定义一个登录页面,为了快速构建登录页面,这里使用了thymeleaf模板。

    第一步,在配置文件中,引入thymeleaf架包
    <!-- 导入Spring Boot的thymeleaf依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
    
    第二步,在resouce的templates下建立登录页面login.html
    <!DOCTYPE HTML>
    <!-- thymeleaf模板必须引入 -->
    <!DOCTYPE HTML>
    <!-- thymeleaf模板必须引入 -->
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>SpringBoot模版渲染</title>
        <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
    </head>
    <body>
    <form th:action="@{/login}" method="post" >
        <input th:text="用户名" type="text" name="username" />
        <input th:text="密码" type="password" name="password" />
        <button type="submit" >提交</button>
        <!-- ${session?.SPRING_SECURITY_LAST_EXCEPTION?.message} security自带的错误提示信息 -->
        <p th:if="${param.error}" th:text="${session?.SPRING_SECURITY_LAST_EXCEPTION?.message}" ></p>
    </form>
    </body>
    </html>
    
    第三步,添加security的配置文件,为了debug登录情况,对UserDetails、MyPasswordEncoder、UserDetailsService三个接口进行了实现,并在配置文件中进行了配置
    package com.example.demo.security;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.web.csrf.CsrfFilter;
    import org.springframework.web.filter.CharacterEncodingFilter;
    
    /**
     * SpringSecurity的配置
     * @author 程就人生
     * @date 2019年5月26日
     */
    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        
        @Autowired
        private UserDetailsService myCustomUserService;
    
        @Autowired
        private MyPasswordEncoder myPasswordEncoder;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            
            http.cors().and().csrf().disable();
            
            http
                //使用form表单post方式进行登录
                .formLogin()
                //登录页面为自定义的登录页面
                .loginPage("/login")
                //设置登录成功跳转页面,error=true控制页面错误信息的展示
                .successForwardUrl("/index").failureUrl("/login?error=true")
                .permitAll()
                .and()
                //允许不登陆就可以访问的方法,多个用逗号分隔
                .authorizeRequests().antMatchers("/test").permitAll()
                //其他的需要授权后访问
                .anyRequest().authenticated();        
            
                //session管理,失效后跳转  
                http.sessionManagement().invalidSessionUrl("/login"); 
                //单用户登录,如果有一个登录了,同一个用户在其他地方登录将前一个剔除下线 
                //http.sessionManagement().maximumSessions(1).expiredSessionStrategy(expiredSessionStrategy()); 
                //单用户登录,如果有一个登录了,同一个用户在其他地方不能登录 
                http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true); 
                //退出时情况cookies
                http.logout().deleteCookies("JESSIONID"); 
                //解决中文乱码问题 
                CharacterEncodingFilter filter = new CharacterEncodingFilter(); 
                filter.setEncoding("UTF-8"); filter.setForceEncoding(true); 
                //
                http.addFilterBefore(filter,CsrfFilter.class); 
        }
        
    //  @Bean
    //  public SessionInformationExpiredStrategy expiredSessionStrategy() {
    //      return new ExpiredSessionStrategy();
    //  }
        
        @Bean
        public DaoAuthenticationProvider daoAuthenticationProvider() {
            DaoAuthenticationProvider bean = new DaoAuthenticationProvider();
            //返回错误信息提示,而不是Bad Credential
            bean.setHideUserNotFoundExceptions(true);
            //覆盖UserDetailsService类
            bean.setUserDetailsService(myCustomUserService);
            //覆盖默认的密码验证类
            bean.setPasswordEncoder(myPasswordEncoder); 
            return bean;
        }
    
        @Override
        public void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.authenticationProvider(this.daoAuthenticationProvider());
        }
    }
    

    在这个配置中,对登录页面进行了设置,设置使用自定义的登录页面,在Controller需要添加对应的页面渲染。

    package com.example.demo.controller;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.servlet.ModelAndView;
    /**
     * 
     * @author 程就人生
     * @date 2019年6月8日
     */
    @RestController
    public class IndexController {
        
        @RequestMapping("/index")
        public ModelAndView index(){
            return new ModelAndView("/index");
        }
        
        @RequestMapping("/test")
        public Object test(){
            return "test";
        }
        
        /**
         * 自定义登录页面
         * @param error 错误信息显示标识
         * @return
         *
         */
        @GetMapping("/login")
        public ModelAndView login(String error){
             ModelAndView modelAndView = new ModelAndView("/login");
             modelAndView.addObject("error", error);
            return modelAndView;
        }   
    }
    
    package com.example.demo.security;
    
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    
    /**
     * 登录专用类,用户登陆时,通过这里查询数据库
     * 自定义类,实现了UserDetailsService接口,用户登录时调用的第一类
     * @author 程就人生
     * @date 2019年5月26日
     */
    @Component
    public class MyCustomUserService implements UserDetailsService {
    
        /**
         * 登陆验证时,通过username获取用户的所有权限信息
         * 并返回UserDetails放到spring的全局缓存SecurityContextHolder中,以供授权器使用
         */
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            //在这里可以自己调用数据库,对username进行查询,看看在数据库中是否存在
            MyUserDetails myUserDetail = new MyUserDetails();
            if(StringUtils.isEmpty(username)){
                throw new RuntimeException("用户名不能为空!");
            }
            if(!username.equals("admin")){
                throw new RuntimeException("用户名不存在!");
            }
            myUserDetail.setUsername(username);
            myUserDetail.setPassword("123456");
            return myUserDetail;
        }
    }
    
    package com.example.demo.security;
    
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.stereotype.Component;
    
    /**
     * 自定义的密码加密方法,实现了PasswordEncoder接口
     * @author 程就人生
     * @date 2019年5月26日
     */
    @Component
    public class MyPasswordEncoder implements PasswordEncoder {
    
        @Override
        public String encode(CharSequence charSequence) {       
            //加密方法可以根据自己的需要修改
            return charSequence.toString();
        }
    
        @Override
        public boolean matches(CharSequence charSequence, String s) {
            return encode(charSequence).equals(s);
        }
    }
    
    package com.example.demo.security;
    
    import java.util.Collection;
    
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    /**
     * 实现了UserDetails接口,只留必需的属性,也可添加自己需要的属性
     * @author 程就人生
     * @date 2019年5月26日
     */
    public class MyUserDetails implements UserDetails {
    
        private static final long serialVersionUID = 1L;
    
        //登录用户名
        private String username;
        //登录密码
        private String password;
    
        private Collection<? extends GrantedAuthority> authorities;
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
            this.authorities = authorities;
        }
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return this.authorities;
        }
    
        @Override
        public String getPassword() {
            return this.password;
        }
    
        @Override
        public String getUsername() {
            return this.username;
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return true;
        }
    }
    
    
    第四步,重新启动项目,看一下登录页面的效果
    图-4

    一个很丑的登录页面,这不是重点。重点是,登录名和密码正确时,页面可以正确的跳转,输入错误时,可以在登录页面进行信息提示。

    在MyCustomUserService类中,我们设置了用户名为admin,密码为123456;输入其他的用户名称时,提示用户不存在;不输入用户名称,提示用户不能为空;密码不是123456时,提示密码错误;输入admin,123456时,页面前往index页面,下面进行验证。

    第一步,测试不输入用户名,测试结果ok
    图-5
    第二步,测试输入错误的用户名,测试结果ok
    图-6
    第三步,测试输入正确的用户名和错误的密码,测试结果ok
    图-7
    第四步,测试输入正确的用户名和密码,测试结果ok
    图-8
    第五步,在index.html页面中加个退出按钮,测试一下退出操作,这里为了简单,写了一个表单
    <!DOCTYPE HTML>
    <!-- thymeleaf模板必须引入 -->
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>SpringBoot模版渲染</title>
        <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
    </head>
    <body>
    index
    <form th:action="@{/logout}" method="get" >
    <button type="submit" >退出</button>
    </form>
    </body>
    </html>
    
    第六步,不登录,访问index时,是直接跳往登录页面的,登录后直接跳往index页面,测试结果ok
    图-9
    图-10 有帮助,微信扫一扫,关注一下

    相关文章

      网友评论

        本文标题:SpringBoot Security 整合thymeleaf模

        本文链接:https://www.haomeiwen.com/subject/rouqqctx.html