一、传统web开发准备工作
如果不懂原理的话,去看上一篇文章:CSDNhttps://mp.csdn.net/mp_blog/creation/editor/141716695
导入需要的依赖包,在传统web页面开发比较简单,我们设置只需要在页面请求参数加上一个remember-me 即可,值可以为:true,on,yes,1 均可
<dependencies> <!-- web 支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- SpringSecurity依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <!-- 简化get set 方法--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.30</version> </dependency> <!-- mybatis 支持 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <!-- mysql 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
1.1 效果图
1.2 前端代码,自定义登录页面
<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css"> <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script> <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script> <title>登录</title> <style type="text/css"> .form-horizontal{margin-top: 3%;} </style></head><body><form role="form" class="form-horizontal" th:action="@{/doLogin.do}" method="post"> <div class="form-group"> <label class="control-label col-sm-2">账号</label> <div class="col-sm-8"> <input type="text" class="form-control" name="loginId" placeholder="请输入登录账号"> </div> </div> <div class="form-group"> <label class="control-label col-sm-2">密码</label> <div class="col-sm-8"> <input type="text" class="form-control" name="pwd" placeholder="请输入密码"> </div> </div> <!-- <div class="form-group"> <label class="control-label col-sm-2">图形验证码</label> <div class="col-sm-4"> <input type="text" class="form-control" name="verifyImg" placeholder="请输入密码"> </div> <div class="col-sm-4"> <img src="/comm/kaptcha" id="kaptcha_id"> </div> </div>--> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <div class="checkbox"> <label> <input type="checkbox" value="1" name="remember-me">请记住我 </label> </div> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="submit" class="btn btn-default">登录</button> </div> </div></form></body></html>
1.3 后端代码
该地方cookie是基于数据库的,但是可以使用基于内存的方式;
@Configurationpublic class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private DataSource mysqlDataSource;// @Bean// public UserDetailsService userDetailsService() {// InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();// UserDetails userDetails = User.withUsername("root").password("{noop}123456").authorities("admin").build();// userDetailsManager.createUser(userDetails);// return userDetailsManager;// } @Bean public UserDetailsService userDetailsService() { return new LoginUserServiceImpl(); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .mvcMatchers("/").permitAll()// 登录页面可直接访问 .anyRequest().authenticated() .and() .formLogin() .loginProcessingUrl("/doLogin.do") //登录请求url .loginPage("/")//默认登录页面 .failureForwardUrl("/")//登录失败跳转url .defaultSuccessUrl("/hello",true)//登录成功跳转页,总是到 hello页面 .usernameParameter("loginId")//用户名 .passwordParameter("pwd") .and() .rememberMe()//开启记住我的功能 .rememberMeParameter("remember-me")// 记住我请求参数 //.rememberMeServices(rememberMeServices()) // 指定rememberMeServices 实现 .tokenRepository(persistentTokenRepository()) //持久化令牌 .and() .csrf().disable(); } @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl(); repository.setDataSource(mysqlDataSource); //repository.setCreateTableOnStartup(true);//自动创建表结构 return repository;}
1.4 设置session 过期时间为1分钟,看登录后cookie值变化
二、自定义页面记住我(前后端分离)
前后端分离需要自己重写UsernamePasswordAuthenticationFilter 处理json类型的数据,还需要重写AbstractRememberMeServices 中的org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices#rememberMeRequested 中的方法,判断是否是需要记住我,同事需要记传一个UserDetailsService,PersistentTokenRepository 对象;传UserDetailsService的对象是因为记住我解析完cookie之后,会调用 loadUserByUsername的方法重新获取一次用户信息;
2.1 效果图
2.2 代码实现
只有后端代码配置,没有前端页面设置,数据为json格式传输
2.2.1 自定义LoginFilter
用来替代UsernamePasswordAuthenticationFilter 过滤器
public class LoginFilter extends UsernamePasswordAuthenticationFilter { @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (!request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } // 判断请求类型是否为json if (MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(request.getContentType()) || MediaType.APPLICATION_JSON_UTF8_VALUE.equalsIgnoreCase(request.getContentType())) { try { Map<String,String> userInfMap = new ObjectMapper().readValue(request.getInputStream(), Map.class); String username = userInfMap.get(getUsernameParameter()); String password = userInfMap.get(getPasswordParameter()); // 记住我参数 String remembersValue = userInfMap.get(AbstractRememberMeServices.DEFAULT_PARAMETER); request.setAttribute(AbstractRememberMeServices.DEFAULT_PARAMETER,remembersValue); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } catch (IOException e) { e.printStackTrace(); } } // 走默认逻辑 return super.attemptAuthentication(request, response); }}
2.2.2 自定义RememberMeServices
主要是用来重写rememberMeRequested 拿到记住我的请求参数,
public class CustomerRemembersService extends PersistentTokenBasedRememberMeServices { public CustomerRemembersService(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) { super(key, userDetailsService, tokenRepository); } @Override protected boolean rememberMeRequested(HttpServletRequest request, String parameter) { String paramValue = request.getAttribute(parameter).toString(); if (paramValue != null) { if (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on") || paramValue.equalsIgnoreCase("yes") || paramValue.equals("1")) { return true; } } return false; }}
2.2.3 将loginFilter和自定义PersistentTokenBasedRememberMeServices 加入搭配配置中
@Configurationpublic class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private DataSource mysqlDataSource;// @Bean// public UserDetailsService userDetailsService() {// InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();// UserDetails userDetails = User.withUsername("root").password("{noop}123456").authorities("admin").build();// userDetailsManager.createUser(userDetails);// return userDetailsManager;// } @Bean public UserDetailsService userDetailsService() { return new LoginUserServiceImpl(); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginProcessingUrl("/doLogin.do") //登录请求url .and() .rememberMe()//开启记住我的功能 .rememberMeParameter("remember-me")// 记住我请求参数 .rememberMeServices(rememberMeServices()) // 指定rememberMeServices 实现 .and() .exceptionHandling() .authenticationEntryPoint((request, response, authException) -> { // 未认证提示错误信息 Map<String,Object> resMap = new HashMap<>(); resMap.put("code","401"); resMap.put("msg","请认证后再来请求接口!"); WebUtils.writeJson(response,resMap); }) .and() .csrf().disable(); // 替换默认的认证器 http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class); } @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl(); repository.setDataSource(mysqlDataSource); return repository; } @Bean public RememberMeServices rememberMeServices() { return new CustomerRemembersService("uuid"// 定义一个生成令牌的key ,userDetailsService() //认证数据源 ,persistentTokenRepository() // 数据库方式 ); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public LoginFilter loginFilter() throws Exception { LoginFilter loginFilter = new LoginFilter(); loginFilter.setFilterProcessesUrl("/doLogin.do");// 登录请求的url loginFilter.setUsernameParameter("loginId"); loginFilter.setPasswordParameter("pwd"); // 认证成功时候处理 loginFilter.setAuthenticationSuccessHandler(((request, response, authentication) -> { // 认证失败时候处理 Map<String,Object> resMap = new HashMap<>(); resMap.put("code","0000"); resMap.put("msg","登录成功"); resMap.put("data",authentication); WebUtils.writeJson(response,resMap); })); // 认证失败时候处理 loginFilter.setAuthenticationFailureHandler(((request, response, exception) -> { Map<String,Object> resMap = new HashMap<>(); resMap.put("code","401"); resMap.put("msg",exception.getMessage()); WebUtils.writeJson(response,resMap); })); // 设置自定义的认证数据源 loginFilter.setAuthenticationManager(authenticationManagerBean()); // 设置记住我,这个地方的记住非第一次登录时候 loginFilter.setRememberMeServices(rememberMeServices()); return loginFilter; }}
2.3 最终使用postman 测试接口
2.4 remember 请求参数源码
为什么请求参数是yes 或者on 都可以,请求的参数的key为remember-me
三、源码资源地址:
https://download.csdn.net/download/qq_36260963/89695919