• springboot+security自定义登录-1-基础-自定义用户和登录界面


    为何学习spring security? 理由如下:

    1)虽然可以不用,但难免部分客户又要求

    2)某种程度上,security还是不错的,譬如csrf,oauth等等,省了一些功夫。

    3)虽然spring security 比较庞杂,甚至有些臃肿,但权衡之下,还是可以一学!。

    根据很多网络例子和书籍来试验,都没有成功,原因可能是:

    1)某些地方配置错了

    2)使用的版本和他人不同

    费了不少功夫。

    一气之下,直接使用2.3.4的版本,成功了!

    毫无疑问,2.3.4比以往的更加人性化。

    以下内容比较长,可能需要耗费10分钟以上时间阅读。

    自定义的关键在于几点。

    一、pom配置+spring配置

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.4.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>learning</groupId>
        <artifactId>secutiry</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>secutiry</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.3</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-test</artifactId>
                <scope>test</scope>
            </dependency>
            <!--  支持jsp -->
            <!-- 添加 servlet 依赖. -->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <scope>provided</scope>
            </dependency>
            <!-- 添加 JSTL(JSP Standard Tag Library,JSP标准标签库) -->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>jstl</artifactId>
            </dependency>
                    <!-- Jasper是tomcat中使用的JSP引擎,运用tomcat-embed-jasper可以将项目与tomcat分开 -->
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-jasper</artifactId>
                <scope>provided</scope>
            </dependency>
            
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    #热部署
    spring.devtools.livereload.enabled=true
    spring.devtools.restart.enabled=true
    #服务器
    server.port=8888
    #bean 相关
    #bean延迟启动
    spring.main.lazy-initialization=false
    #安全
    #spring.security.user.name=root
    #spring.security.user.password=root
    server.servlet.context-path = /
    #优雅关闭--等待还有的连接完成,之后不再允许有新的请求,类似于一些数据库的操作
    server.shutdown=graceful
    spring.lifecycle.timeout-per-shutdown-phase=20s
    #设置静态资源等
    spring.mvc.static-path-pattern=/**
    spring.resources.static-locations=/css/,/images/,/WEB-INF/plugin/
    
    #启动jsp功能
    spring.mvc.view.prefix=/WEB-INF/jsp/
    spring.mvc.view.suffix=.jsp
    # 数据库连接(mysql)
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url =jdbc:mysql://127.0.0.1:7799/spring?rewriteBatchedStatements=true&autoReconnect=true&allowMultiQueries=true&useSSL=false&serverTimezone=CST&allowPublicKeyRetrieval=true
    spring.datasource.username =lzf
    spring.datasource.password =123
    
    #连接池配置-HikariCp-----------------------------------------------------
    #鉴于springboot目前的版本,spring.datasource.type也可以不写
    spring.datasource.name=hcmdmserverDs
    spring.datasource.type=com.zaxxer.hikari.HikariDataSource
    #是否自动提交,默认true
    spring.datasource.hikari.autoCommit=false
    #连接超时,过了这个时间还连接不到,hikari会返回错误,设置3分钟
    spring.datasource.hikari.connectionTimeout=180000
    #多了多少毫秒不用,会被设置为空闲,默认是10分钟
    spring.datasource.hikari.idleTimeout=600000
    #最大生命周期,默认1800000(30分钟)
    spring.datasource.hikari.maxLifetime=1800000
    #最少空闲连接
    spring.datasource.hikari.minimumIdle=3
    #最大连接池大小=空闲+在用
    spring.datasource.hikari.maximumPoolSize=6
    spring.datasource.hikari.connection-test-query=select 1
    #如果不指定 spring.datasource.type,则以下可以是通用的连接池配置信息
    
    #spring-jdbc
    
    #http-请求连接池

    二、org.springframework.security.core.userdetails.UserDetailsService实现类

    这个部分的关键是两点:

    1)实现UserDetailsService的时候,返回一个UserDetails即可

    2)用户密码不需要像一些地方说的那样要有{bcrypt}之类的前缀

    /**
     * 
     * @author     lzfto
     * @apiNote   
     */
    @Service
    public class UdsDetail implements UserDetailsService {
    
        @Autowired
        MyUserService usersService;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            UserRolePojo ur = usersService.getUserRoleDetail(username);
            return change(ur);
        }
    
        private UserDetails change(UserRolePojo ur) {
            List<GrantedAuthority> authorityList = new ArrayList<GrantedAuthority>();
            List<RolePojo> roleList = ur.getRoleList();
            for (RolePojo role : roleList) {
                GrantedAuthority gt = new SimpleGrantedAuthority(role.getName());
                authorityList.add(gt);
            }
            UserDetails user = new User(ur.getUserPojo().getName(), ur.getUserPojo().getPassword(), authorityList);
            return user;
        }
    }

    至于如何和数据库关联,还是比较简单的,不需赘述!

    此处附上插入用户信息的脚本:

    -- 学生表
    CREATE TABLE `users` (
      `id` int NOT NULL AUTO_INCREMENT,
      `name` varchar(40) NOT NULL,
      `password` varchar(70) NOT NULL,
      `create_time` varchar(20) NOT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `name` (`name`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
    create table role(
      id int not null auto_increment,
      name varchar(40) not null,
      primary key(id)
    );
    
    create table user_role_detail(
     id int not null auto_increment,
     user_id int not null,
     role_id int not null,
     primary key(id)
    );
    create unique index uid_user_role_detail on user_role_detail(user_id,role_id);
    
    
    --
    -- 密码是123
    -- 不需要添加什么 bcrypt之类的前缀,在2.3.4版本中。
    INSERT INTO `spring`.`users` (`name`, `password`, `create_time`) VALUES ('lzf', '$2a$10$Mg8XzxbqsOMQAxrPD8d9hOELzDyGc7lShVdSb7vOLWwEplWlga7cO', '2020-08-11 12:00:00');
    INSERT INTO `spring`.`users` (`name`, `password`, `create_time`) VALUES ('wth', '$2a$10$Mg8XzxbqsOMQAxrPD8d9hOELzDyGc7lShVdSb7vOLWwEplWlga7cO', '2020-08-11 12:00:00');
    --
    INSERT INTO `spring`.`role` (`name`) VALUES ('ADMIN');
    INSERT INTO `spring`.`role` (`name`) VALUES ('MANAGER');
    INSERT INTO `spring`.`role` (`name`) VALUES ('LEADER');
    INSERT INTO `spring`.`role` (`name`) VALUES ('CEO');
    --
    insert into user_role_detail(user_id,role_id) select u.id,r.id from users u,role r where u.name='lzf';
    insert into user_role_detail(user_id,role_id) select u.id,r.id from users u,role r where u.name='wth' and r.name!='ADMIN';

    三、WebSecurityConfigurerAdapter等有关配置

    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        UdsDetail uService;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            /**
             * 1.需要把loginPage,loginProcessingUrl放开 permiAll,否则会进入无限循环重定向
             * 2.antMatchers("/doLogin", "/touch","/error404").permitAll()的顺序不重要
             * 3.无需要配置 scanBasePackages
             */
            http
            .formLogin()
            .loginPage("/doLogin")
            .loginProcessingUrl("/touch")
            .and()
            .authorizeRequests()
            .antMatchers("/doLogin", "/touch","/error404","/plugin/*").permitAll()
            .anyRequest().authenticated()
            .and()
            .csrf().disable();
        }
        
        
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            PasswordEncoder encoder = new BCryptPasswordEncoder();
            auth.userDetailsService(uService).passwordEncoder(encoder);
        }
    
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers("/plugin/**", "/css/**", "/images/**");
        }
    
    }

    第一次看别人的"configure(HttpSecurity http)"方法的时候,

    总有疑问:

    •    "loginProcessingUrl"是不是一定是/login?
    • 是不是可以是其它的?
    • 就算是/login,那么是否需要在控制器中定义一个对应的方法?

    明确的答案见最后一个小节:“创建login.jsp”,此处不再赘述。

    为了让系统找到/doLogin,必须定义一个控制器方法

        @RequestMapping("/doLogin")
        public ModelAndView skipLogin() {
            ModelAndView mv = new ModelAndView("login");
            return mv;
        }

    有的人不喜欢使用控制器,直接使用某个页面替代,譬如的login.jsp。这就是存粹的个人习惯了!

    应用启动类:

    @SpringBootApplication
    public class SecutiryApplication {
        public static void main(String[] args) {
            SpringApplication.run(SecutiryApplication.class, args);
        }
    
    }

     SpringBootApplication无需具有 scanBasePackages的语法,因为那样会导致springboot使用默认的WebSecurityConfigurerAdapter 覆盖用户自己自定义的类,譬如上文的WebSecurityConfig

    四、模板引擎,个人推荐使用jsp

    为什么使用springboot+jsp,是因为springboot搭建mvc的确方便,其次jsp是公司大部分人都会,都熟悉的语言,而thymeleaf之类的模板引擎

    额外增加了学习成本,但我们的项目并没有那么cloud。

    所以综合起来,使用springboot+jsp是不错的

    五、目录结构

    在src/main/下创建:

    /src/main/webapp/WEB-INF

    /src/main/webapp/WEB-INF/jsp   (放jsp文件)

    /src/main/webapp/plugin     (放jsp第三方插件,譬如jquery,vue,bootstrap等)

    /src/main/webapp/css        (放公共样式)

    /src/main/webapp/resource  (放图片等资源)

    六、创建登录页面login.jsp(放在/src/main/webapp/WEB-INF/jsp下面)

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <!DOCTYPE html>
    <html >
    <head>
        <meta charset="UTF-8">
        <title>登录测试页面(jsp)</title>
    </head>
    
    <body>
        <h2>自定义登录表单(jsp)</h2>
       <div>   
            <br />
            <!-- action 叫什么无所谓,只需要和 WebSecurityConfigurerAdapter 中的 loginProcessingUrl 值一致即可  -->
            <form method="post" action="/touch">
                用户名:
                <input type="text"  id="username" name="username" placeholder="name"><br />
                密码:
                <input type="password"  id="password" name="password" placeholder="password"><br />
                <input type="button"   value="提交"  onclick="fnLogin()">
                <!-- <input type="submit"  value="提交" > -->
            </form>
            
      </div>   
    </body>
    <script type="text/javascript" src="/plugin/jquery-3.4.1.min.js"></script>
    <script>
    function fnLogin(){
        let uName=$("#username").val();
        let pwd=$("#password").val();
        $.ajax({
            method: "post",
            url: "/login",
            cache: false,
            async: false,
            data: {
                "username":uName,
                "password":pwd
            },        
            success: function (data) {       
                    //location.href ="/test/main";
                    alert("good");
            },
            error: function (data) {
                console.log(data);
            }
        });
    }
    </script>
    
    </html>

    如果想使用form提交,那么,提交按钮如下设置:

    <!-- <input type="button"   value="提交"  onclick="fnLogin()"> -->
         <input type="submit"  value="提交" >

    反之,如果想使用ajax请求,那么对上面的语句反向注释即可:

     <input type="button"   value="提交"  onclick="fnLogin()">
     <!-- <input type="submit"  value="提交" > -->

    使用ajax请求的关键在于设定参数和url。
    注:如果需要这么使用,必须保证先继承UsernamePasswordAuthenticationFilter或者那个抽象父类AbstractAuthenticationProcessingFilter ,改写有关内容。
    如果不是很有必要,就还是老老实实使用默认的form提交。



    而url在没有修改的情况下是默认指定为/login,这是 在 UsernamePasswordAuthenticationFilter已经定义了,如下文:
    /*
     * @author Ben Alex
     * @author Colin Sampaleanu
     * @author Luke Taylor
     * @since 3.0
     */
    public class UsernamePasswordAuthenticationFilter extends
            AbstractAuthenticationProcessingFilter {
        // ~ Static fields/initializers
        // =====================================================================================
    
        public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
        public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
    
        private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
        private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
        private boolean postOnly = true;
    
        // ~ Constructors
        // ===================================================================================================
    
        public UsernamePasswordAuthenticationFilter() {
            super(new AntPathRequestMatcher("/login", "POST"));
        }
    那么是否可以不用/login? 答案是可以的! 
    怎么做? 继承AbstractAuthenticationProcessingFilter ,然后在自定义的WebSecurityConfigurerAdapter 中配置一个新的过滤器。
    假定这个继承的过滤器叫MyAuthFilter,那么MyAuthFilter在完成验证之后,就直接绕过原有的UsernamePasswordAuthenticationFilter等后续验证。
    当然这样的做法并不是太好!
    但这是为了告诉我们验证url是可以修改的,而不必都是/login。

  • 相关阅读:
    数组逆序输出与简单的数据匹配
    冒泡排序及其优化
    类型转换
    Spring学习【Spring】
    logistic回归模型
    决策树算法(3)
    决策树算法(2)
    决策树算法(1)
    朴素贝叶斯算法
    k近邻算法
  • 原文地址:https://www.cnblogs.com/lzfhope/p/spring_security.html
Copyright © 2020-2023  润新知