zoukankan      html  css  js  c++  java
  • spring boot项目14:安全-基础使用(1)

    JAVA 8

    Spring Boot 2.5.3

    MySQL 5.7.21(单机)

    ---

    授人以渔:

    1、Spring Boot Reference Documentation

    This document is also available as Multi-page HTML, Single page HTML and PDF.

    有PDF版本哦,下载下来!

    2、Spring Security Reference

    PDF版本哦(网页版末尾的 /html5/ 改为 /pdf/),下载下来!

    目录

    1、安全初体验

    2、自定义表单登录页

    3、多用户、角色、认证

    使用InMemoryUserDetailsManager

    使用JdbcUserDetailsManager

    4、自定义数据库模型

    用户过期试验

    5、安全之Session

    参考文档

    本文使用项目:

    mysql-hello

    Web项目,底层使用MySQL存储数据,默认端口30000。

    MySQL配置——后面会用到:

    数据库配置
    #
    # MySQL on Ubuntu
    spring.datasource.url=jdbc:mysql://mylinux:3306/db_example?serverTimezone=Asia/Shanghai
    spring.datasource.username=springuser
    spring.datasource.password=ThePassword
    #spring.datasource.driver-class-name =com.mysql.jdbc.Driver # This is deprecated
    spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver
    spring.jpa.hibernate.ddl-auto=update
    spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
    # 打开使用过程中执行的SQL语句
    spring.jpa.show-sql: true

    1、安全初体验

    添加依赖包 spring-boot-starter-security:

    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    包结构:

    启动项目,此时,任何链接都不能访问。

    启动日志:

    Using generated security password 后面是 默认用户user的密码。

    在浏览器中访问,弹出登录对话框:

    登录页-源码
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta 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>Please sign in</title>
        <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
        <link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
      </head>
      <body>
         <div class="container">
          <form class="form-signin" method="post" action="/login">
            <h2 class="form-signin-heading">Please sign in</h2>
            <p>
              <label for="username" class="sr-only">Username</label>
              <input type="text" id="username" name="username" class="form-control" placeholder="Username" required autofocus>
            </p>
            <p>
              <label for="password" class="sr-only">Password</label>
              <input type="password" id="password" name="password" class="form-control" placeholder="Password" required>
            </p>
    <input name="_csrf" type="hidden" value="ed3f49ac-647f-4a59-b2e3-b24498725774" />
            <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
          </form>
    </div>
    </body></html>

    源码里面有一个提交数据 /login 的表单——实现登录。

    输入 user、日志中的密码,登录成功。

    除了上面的 /login 实现登录,还有一个 /logout 端点实现 退出登录:

    随机密码,而且存在日志里面,不好。配置下面的可以实现固定用户及密码:

    # 安全
    spring.security.user.name=lib
    spring.security.user.password=123

    再次启动,日志没有密码信息了。

    浏览器登录,使用上面配置的 lib、123即可。

    小结,

    上面的项目很简单,但有一定实用性了

    2、自定义表单登录页

    登录页:login.html

    static/login.html
    <html>
    <head>
    	<title>login:mysql-hello</title>
    	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    	<style>
    		body {
    			background: #ddd;
    		}
    	</style>
    </head>
    <body>
    <div>请登录:</div>
    <form action="login.html" method="post">
    	<div>用户名:<input type="text" name="username" placeholder="用户名" /></div>
    	<div>密码:<input type="password" name="password" placeholder="密码" /></div>
    	<div><a href="#">忘记密码?</a></div>
    	<div><input type="submit" value="登录" /> </div>
    </form>
    <br />
    <br />
    <div><a href="#">新用户注册</a></div>
    </body>
    </html>

    注,包含username, password的<input>,注意<form>的action和method来自博客园

    添加 AppWebSecurityConfig.java,继承 WebSecurityConfigurerAdapter 并重写 configure(HttpSecurity http)

    @EnableWebSecurity
    public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    	/**
    	 * 自定义登录页:login.html
    	 */
    	@Override
    	protected void configure(HttpSecurity http) throws Exception {
    		http.authorizeRequests()
    				.anyRequest().authenticated()
    				.and()
    			.formLogin()
    				// 自定义登录页
    				.loginPage("/login.html")
    				.permitAll()
    				.and()
    			.csrf().disable();
    	}
    }

    登录页面:

    输入前面配置文件中的用户名、密码,登录成功(首页没有建,显示status=404),但可以测试其它链接的。

    指定处理登录的URL-未通过

    在formLogin()下,指定处理登录的URL:

    .formLogin()
    	// 自定义登录页
    	.loginPage("/login.html")
    	// 处理登录请求的URL
    	.loginProcessingUrl("/login")

    但是,测试失败,登录未成功。

    错误信息
    浏览器页面:
    Whitelabel Error Page
    This application has no explicit mapping for /error, so you are seeing this as a fallback.
    
    Sat Sep 04 23:03:14 CST 2021
    There was an unexpected error (type=Method Not Allowed, status=405).
    ---
    
    应用日志:
    Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported]
    Completed 405 METHOD_NOT_ALLOWED
    "ERROR" dispatch for POST "/error", parameters={masked}

    疑问

    为什么呢?默认登录页的 action不就是 “/login” 吗?怎么这里配置了就不行呢?

    像上面配置后,默认的/login 无效了?需要自己写?怎么写?格式呢?TODO

    登录返回值

    上面的试验中,登录成功后,跳转到首页。在真实的前后端分离系统中,登录后一般返回 成功与否的信息,比如,一段JSON数据,再由前端决定怎么处理——跳转到哪里。

    在formLogin()下,配置 successHandler、failureHandler 分别实现登录成功、失败后的逻辑。来自博客园

    .formLogin()
    	// 自定义登录页
    	.loginPage("/login.html")
    	// 处理登录请求的URL
    	// 指定后登录失败,注释掉,TODO
    //				.loginProcessingUrl("/login")
    	// 登录成功的处理
    	.successHandler(new AuthenticationSuccessHandler() {
    		
    		@Override
    		public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp,
    				Authentication auth) throws IOException, ServletException {
    			resp.setContentType("application/json;charset=utf-8");
    			PrintWriter out = resp.getWriter();
    			out.write(ResultVO.getSuccess("登录成功").toString());
    		}
    	})
    	// 登录失败的处理
    	.failureHandler(new AuthenticationFailureHandler() {
    		
    		@Override
    		public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp,
    				AuthenticationException ex) throws IOException, ServletException {
    			resp.setContentType("application/json;charset=utf-8");
    			resp.setStatus(HttpStatus.UNAUTHORIZED.value());
    			PrintWriter out = resp.getWriter();
    			out.write(ResultVO.getFailed(HttpStatus.UNAUTHORIZED.value(), "登录失败", "请重新登录").toString());
    		}
    	})
    	.permitAll()
    	.and()

     注,ResultVO 是项目的一个 统一返回对象类,getSuccess、getFailed是其中的静态方法

    测试结果:成功

    3、多用户、角色、认证

    前面的章节,只有一个用户。本章介绍多个用户的使用。

    自定义一个 UserDetailsService Bean即可。

    接口有很多实现类,其中:来自博客园

    1)InMemoryUserDetailsManager 的用户数据 存储到 内存,重启后丢失

    2)JdbcUserDetailsManager 的用户数据 存储到 数据库,比如,MySQL数据库

    使用InMemoryUserDetailsManager 

    准备3个接口:

    /security/admin/hello 需要ADMIN角色的用户才可以访问

    /security/user/hello 需要USER角色的用户才可以访问

    /security/app/hello 任意登录用户都可以访问

    SecurityAdminController.java
    @RestController
    @RequestMapping(value="/security/admin")
    @Slf4j
    public class SecurityAdminController {
    
    	@GetMapping(value="/hello")
    	public String hello() {
    		return "hello, Admin";
    	}
    	
    }

    其它两个Controller类似。来自博客园

    更改 AppWebSecurityConfig:

    之前的configure函数做了改动;

    增加了 UserDetailsService Bean的生成函数,并增加了2个用户对应不同的角色;

    passwordEncoder函数 在 本文使用的 S.B.版本是必须的,否则发生异常,,但这个NoOpPasswordEncoder过期了,,原因及解决方案有待进一步研究,TODO

    	/**
    	 * 试验2:资源授权
    	 */
    	@Override
    	protected void configure(HttpSecurity http) throws Exception {
    		http.authorizeRequests()
    				// 使用角色
    				.antMatchers("/security/admin/**").hasRole("ADMIN")
    				.antMatchers("/security/user/**").hasRole("USER")
    				.antMatchers("/security/app/**").permitAll()
    				.anyRequest().authenticated()
    				.and()
    			.formLogin().permitAll()
    				.and()
    			.csrf().disable();
    	}
    	
    	/**
    	 * 基于内存数据库的用户信息
    	 */
    	@Bean
    	public UserDetailsService userDetailsService() {
    		// 基于内存的用户信息:2个用户,不同角色
    		InMemoryUserDetailsManager man = new InMemoryUserDetailsManager();
    		man.createUser(User.withUsername("user").password("123").roles("USER").build());
    		man.createUser(User.withUsername("admin").password("123").roles("ADMIN").build());
    		return man;
    	}
    	
    	/**
    	 * 必须有,否则发生异常
    	 * 是否可以使用其它 PasswordEncoder 的实现类呢?
    	 * 据说是 5.X版本之后默认启用了 委派密码编码器 导致
    	 * @author ben
    	 * @date 2021-09-05 00:10:49 CST
    	 * @return
    	 */
    	@Bean
    	public PasswordEncoder passwordEncoder() {
    		// 过时了?怎么弄?TODO
    		// 因为不安全,只能用于测试、明文密码验证等,故废弃
    		return NoOpPasswordEncoder.getInstance();
    	}

    注意,上面的配置后,配置文件中的 lib 用户就不能使用了

    启动应用,测试:

    user、admin分别访问前面的 3个接口。

    用户/接口 user admin
    /security/admin/hello type=Forbidden, status=403 hello, Admin
    /security/user/hello hello, User type=Forbidden, status=403
    /security/app/hello hello, APP hello, APP

    符合预期。来自博客园

    更进一步:

    动态管理用户(增删改查),或可以使用 容器中的 userDetailsService Bean——即上面配置生成了。

    转换为 InMemoryUserDetailsManager 后进行操作。

    不过,应用重启后,这些用户数据丢失,意义不大,但从接口来看是可以做到的。

    使用JdbcUserDetailsManager

    引入:来自博客园

    <!-- 使用JdbcUserDetailsManager时引入,没有JPA的吗? -->
    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
    	<groupId>mysql</groupId>
    	<artifactId>mysql-connector-java</artifactId>
    	<scope>runtime</scope>
    </dependency>

    注,本项目中,mysql-connector-java早已引入

    在MySQL建立数据表:找到 JdbcUserDetailsManager 类 对应的jar包(spring-security-core),DDL文件位于 同一个jar包的 org.springframework.security.core.userdetails.jdbc.users.ddl

    拷贝其中的语句,改其中的 varchar_ignorecase 为 varchar类型——MySQL支持。来自博客园

    使用改造后的语句到MySQL终端去执行:下图展示执行成功,建立了两张表 users、authorities

    改造 AppWebSecurityConfig 的userDetailsService函数:

    	/**
    	 * 使用JdbcUserDetailsManager
    	 * 本应用的底层为 MySQL数据库——上面的dataSource
    	 */
    	@Bean
    	public UserDetailsService userDetailsService() {
    		JdbcUserDetailsManager man = new JdbcUserDetailsManager();
    		System.out.println("dataSource=" + dataSource);
    		man.setDataSource(dataSource);
    		man.createUser(User.withUsername("user").password("123").roles("USER").build());
    		man.createUser(User.withUsername("admin").password("123").roles("ADMIN").build());
    		return man;
    	}
    

    测试 两个用户对前面3个接口的权限:测试成功,符合预期

    注,上面的 dataSource是 HikariPool-1:

    JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. 
    Therefore, database queries may be performed during view rendering. Explicitly configure 
    spring.jpa.open-in-view to disable this warning
    dataSource=HikariDataSource (HikariPool-1)

    启动后,新建数据表的数据:

    注意,角色创建时是 user、admin,但在 数据库里面是 以“ROLE_”开头

    再次启动应用,发生异常,启动失败,因为 user、admin在数据库中已经存在了。来自博客园

    改造userDetailsService()函数:多了用户存在性判断

    	@Bean
    	public UserDetailsService userDetailsService() {
    		JdbcUserDetailsManager man = new JdbcUserDetailsManager();
    		man.setDataSource(dataSource);
    		
    		if (!man.userExists("user")) {
    			man.createUser(User.withUsername("user").password("123").roles("USER").build());
    		}
    		if (!man.userExists("admin")) {
    			man.createUser(User.withUsername("admin").password("123").roles("ADMIN").build());
    		}
    		return man;
    	}

    默认的数据库模型肯定无法满足生产的需求,比如,里面的密码都没有加密。

    Spring Security具有优良的扩展性,可以很好地实现自定义的数据库模型。

    ---210905 01:55---写到这儿了---

    4、自定义数据库模型

    在使用JdbcUserDetailsManager的默认数据库模型时,用户、权限是分成两张表的。来自博客园

    本章介绍 基于自定义数据库模型的认证和授权。

    两个步骤:1)实现UserDetails——用户详情;2)实现UserDetailsService——用户详情服务(类似于前面的2个Manager);

    cofigure函数保持不变。

    AppUser类,用户实体类,也实现了 UserDetails 接口。

    AppUser.java
    package org.lib.mysqlhello.security.self;
    
    import java.util.Collection;
    import java.util.Date;
    import java.util.List;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.Transient;
    
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * 自定义用户
     * @author ben
     * @date 2021-09-05 09:26:11 CST
     */
    @Entity
    @Data
    @Slf4j
    public class AppUser implements UserDetails {
    
    	private static final long serialVersionUID = 210905L;
    
    	@Id
    	@GeneratedValue(strategy = GenerationType.IDENTITY)
    	private Long id;
    
    	@Column(columnDefinition = "VARCHAR(50) NOT NULL UNIQUE")
    	private String username;
    	
    	@Column(columnDefinition = "VARCHAR(384) NOT NULL")
    	private String password;
    
    	/**
    	 * 用户角色
    	 * 多个角色使用英文都好(,)隔开
    	 */
    	@Column(columnDefinition = "VARCHAR(500) NOT NULL")
    	private String roles;
    	
    	/**
    	 * 用户是否启用:默认启用
    	 */
    	@Column(columnDefinition = "BIT(1) DEFAULT true")
    	private Boolean enabled;
    
    	/**
    	 * 有效期时间戳
    	 * 默认为0 永久有效
    	 */
    	@Column(columnDefinition = "BIGINT DEFAULT 0")
    	private Long expiration;
    	
    	/**
    	 * 创建时间
    	 */
    	@Column(insertable = false, columnDefinition = "DATETIME DEFAULT NOW()")
    	private Date createTime;
    	
    	/**
    	 * 更新时间
    	 */
    	@Column(insertable = false, updatable = false, columnDefinition = "DATETIME DEFAULT NOW() ON UPDATE NOW()")
    	private Date updateTime;
    
    	// ----实现UserDetails接口----
    	
    	// set函数已使用 @Data 注解建立
    	@Transient
    	private List<GrantedAuthority> authorities;
    	
    	@Override
    	public Collection<? extends GrantedAuthority> getAuthorities() {
    		return authorities;
    	}
    
    	@Override
    	public boolean isAccountNonExpired() {
    		if (this.expiration <= 0) {
    			return true;
    		}
    		
    		if (this.expiration >= System.currentTimeMillis()) {
    			return true;
    		}
    		
    		log.warn("用户过期:id={}, expiration={}", this.id, this.expiration);
    		return false;
    	}
    
    	@Override
    	public boolean isAccountNonLocked() {
    		return true;
    	}
    
    	@Override
    	public boolean isCredentialsNonExpired() {
    		return true;
    	}
    
    	@Override
    	public boolean isEnabled() {
    		return this.enabled;
    	}
    	
    	// ----实现UserDetails接口----
    	
    }
    

    启动应用,数据表建好了:

    插入两条数据(用户):

    -- 和之前不同,admin有两个角色哦
    insert into app_user(username, password, roles) values("admin", "123", "ROLE_ADMIN,ROLE_USER");
    insert into app_user(username, password, roles) values("user", "123", "ROLE_USER");

    AppUserDetailsService类:实现了 UserDetailsService接口,并使用 @Service注解。来自博客园

    @Service
    public class AppUserDetailsService implements UserDetailsService {
    
    	@Override
    	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    		
    		return null;
    	}
    
    }

    上面的 AppUserDetailsService Bean 还无法使用:

    前一章 的 userDetailsService() 函数也生成了 userDetailsService Bean,此时,虽然应用可以启动,但是,无法登录——因为有两个 userDetailsService Beans吧。

    注释掉AppWebSecurityConfig类 的 userDetailsService() 函数。来自博客园

    启动应用,登录:AppUserDetailsService 还没写完导致

    继续改造 AppUserDetailsService...

    改造后的 AppUserDetailsService:来自博客园

    package org.lib.mysqlhello.security.self;
    
    import java.util.Objects;
    import java.util.function.Consumer;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.authority.AuthorityUtils;
    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.Service;
    
    /**
     * AppUserDetailsService
     * @author ben
     * @date 2021-09-05 10:34:23 CST
     */
    @Service
    public class AppUserDetailsService implements UserDetailsService {
    
    	@Autowired
    	private AppUserDAO appUserDao;
    	
    	private Consumer<Object> cs = System.out::println;
    	
    	@Override
    	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    		AppUser user = appUserDao.findByUsername(username);
    		cs.accept("user 1=" + user);
    		if (Objects.isNull(user)) {
    			throw new UsernameNotFoundException("用户不存在");
    		}
    		
    		// 权限集
    		// 使用Spring Security的AuthorityUtils:默认支持 英文逗号分开的权限集
    		user.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRoles()));
    		
    		cs.accept("user 1=" + user);
    		return user;
    	}
    
    }
    

    启动应用,测试已添加的用户admin、user访问各个接口:成功,符合预期来自博客园

    UsernameNotFoundException说明

    继承了 AuthenticationException——其下有若干的异常。

    用户过期试验

    在AppUserDetailsService#loadUserByUsername函数中抛出用户过期异常

    失败了

    看来不是这么用的。来自博客园

    记得 AppUser 实现 UserDetails接口 时,有一个 isAccountNonExpired() 函数,或许,过期的判断已经实现了。

    设置user过期时间——30秒有效期:

    -- 当前时间+30秒过期
    -- 注意使用 (unix_timestamp(now())+30)*1000!
    -- 最开始只使用 now() 时验证失败/sad
    mysql> update  app_user set expiration=(unix_timestamp(now())+30)*1000 where id = 2;

    在执行上面的语句后,启动应用,使用 user登录:登录成功。

    30秒后继续操作,可以继续操作,没有被阻止。TODO

    30秒后,在另一个浏览器重新登录:登录失败,提示账号过期。

    回到之前已登录的浏览器操作:可以继续,但会输出 isAccountNonExpired() 函数的 过期日志:来自博客园

    可是,怎么阻止过期用户继续操作啊?!

    5、安全之Session

    用户登录了,登录前后发生了什么?为何服务器知道浏览器登录了?是用哪个账户登录的呢?

    这就涉及到 浏览器(客户端)的Cookie 和 服务器端的Session了。

    启动服务器,访问任一页面,浏览器产生一个名为JSESSIONID的session(Google的Chrome浏览器,按F12查下控制台,选择Application下看Cookie):

    登录后,这个session改变了:

    之后其它操作时,浏览器都把这个JSESSIONID作为Cookie头发送到服务器:

    服务器从这个Cookie头获取JSESSIONID信息,并以此判断(检查应用中存储的session)用户是否登录。

    使用spring security后,启动时会建一个名为 springSecurityFilterChain 的Bean——安全过滤器链,其下存在13个安全过滤器:

    [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@130a99fe,
    org.springframework.security.web.context.SecurityContextPersistenceFilter@59ebe484, 
    org.springframework.security.web.header.HeaderWriterFilter@4458887d, 
    org.springframework.security.web.authentication.logout.LogoutFilter@6c3830ed, 
    org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@2d2710a8, 
    org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@5122387, 
    org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@31773d5b, 
    org.springframework.security.web.savedrequest.RequestCacheAwareFilter@1cafb30, 
    org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@719e8f9f, 
    org.springframework.security.web.authentication.AnonymousAuthenticationFilter@5bbf8daa, 
    org.springframework.security.web.session.SessionManagementFilter@4c364a9d, 
    org.springframework.security.web.access.ExceptionTranslationFilter@7a6078d, 
    org.springframework.security.web.access.intercept.FilterSecurityInterceptor@1d642682]

    其中就有一个 类型为 SessionManagementFilter 的——管理Session使用吗?

    在SessionManagementFilter 类的 doFilter添加断点,调试可知 登录前后session管理 详情

    启动应用后,首次访问,session为null:

    之后执行 SessionManagementFilter 的:

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

    第一轮调试完毕,登录页被打开:此时,浏览器已经存在session了——上面的session一定是在 SessionManagementFilter 执行后的某一步建立的,TODO

    输入用户名、密码登录,进入应用调试,此时,已经可以获取浏览器中的session了:

    登录成功后,session没有发生变化。

    注意

    这里有一个问题,上面调试时session在登录页打开、登录后没有发生变化,但在非调试时,却是 变了的;

    再次调试——使用F8加快进度,这时,session和正常情况一样,变了。TODO

    除了安全的 springSecurityFilterChain Bean外,Web应用本身也有一些Filter,这些Filter有什么用?TODO

    后面找到了 再补充吧!

    关于 session ,spring boot可以使用 server.servlet.session.* 进行配置,包括设置 cookie 的有效时间等。

    本文的session是单机版,服务启动后,session消失;同应用的其它实例无法共享……还需继续研究。

    》》》全文完《《《来自博客园

    补充:

    public interface UserDetails extends Serializable

    其下的User类

    public class User implements UserDetails, CredentialsContainer {

    public interface UserDetailsService

    后记:

    密码没有加密啊?

    阻止过期用户继续访问啊?来自博客园

    记住用户?记住用户多长时间?

    登录过程中都做了什么?过滤器、拦截器啥的?

    自动登录呢?

    基于token的登录呢?

    ……

    看来,还要搞更多试验、更多学习才是啊!

    后面再写一篇好了。来自博客园

    参考文档

    1、《Spring Security实战》

    书,作者:陈木鑫,2019年8月第1版

    非常感谢。

    2、

  • 相关阅读:
    Python命令行下退格、删除、方向键乱码问题解决(亲测有效)
    提取MySQL binlog中指定表的操作记录
    windows下面安装Python和pip终极教程
    day1-作业2
    day1-作业
    centos6.5python2.6环境下安装ansible
    安装inotify-tools,用inotifywait命令监听文件或目录的访问信息(自动同步文件夹)
    GitHub开源MySQL Online DDL工具gh-ost参数解析
    GitHub开源MySQL Online DDL工具gh-ost安装文档
    mysql磁盘IO%util 居高不下之RAID卡 BBU Learn Cycle周期
  • 原文地址:https://www.cnblogs.com/luo630/p/15204990.html
Copyright © 2011-2022 走看看