一、传统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是基于数据库的,但是可以使用基于内存的方式;
@Configuration
public 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 加入搭配配置中
@Configuration
public 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
三、源码资源地址:
版权归原作者 星空寻流年 所有, 如有侵权,请联系我们删除。