1.引言
Spring Security是一个框架,提供 认证(authentication)、授权(authorization) 和 保护,以抵御常见的攻击。它对保护命令式和响应式应用程序有一流的支持,是保护基于Spring的应用程序的事实标准。前两篇简单介绍了一下使用Spring Security 使用Http Basic登录,以及Spring Security如何自定义登录逻辑。
精通Spring Boot: Spring Security自定义登录
精通Spring Boot:Spring Security使用Http Basic认证
这篇文章主要介绍如何使用handler来定义认证相关的流程。
2.spring security 自定义登录页
先做一些自定义的操作,如配置自定义登录页,配置登录请求URL等。
当我们使用Spring Security时,它会为我们提供一个默认的登录页面,这显然没法满足我们的需求,那如何来自定义页面呢?请看代码:
/**
* @author developlee
* @since 2018/11/27 21:58
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final MyLoginHandler myLoginHandler;
private final MyLogoutHandler myLogoutHandler;
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
private MyUserDetailsService myUserDetailsService;
@Autowired
public SecurityConfig(MyLoginHandler myLoginHandler, MyLogoutHandler myLogoutHandler) {
this.myLoginHandler = myLoginHandler;
this.myLogoutHandler = myLogoutHandler;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义用户登录页,并允许客户端请求
http.formLogin().loginPage("/login").permitAll()
.loginProcessingUrl("/sign_in")
// 配置登录成功的handler
.successHandler(myLoginHandler)
.and().authorizeRequests().anyRequest().authenticated();
// 配置登出的handler
http.logout().addLogoutHandler(myLogoutHandler)
// logout 成功,删除 cookies
.deleteCookies("web-site", "custom-token").clearAuthentication(true);
// Spring Security 默认是开启了CSRF 保护的,所以logout操作必须是用POST方式请求,
// 如果非要使用GET请求来logout的话,也可以在代码中的实现
//.logoutRequestMatcher(new AntPathRequestMatcher("/logout","GET"))
//session管理 session失效后跳转
http.sessionManagement().invalidSessionUrl("/login");
//只允许一个用户登录,如果同一个账户两次登录,那么第一个账户将被踢下线,跳转到登录页面
http.sessionManagement().maximumSessions(1).expiredUrl("/login");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder());
auth.eraseCredentials(false);
}
}
接下来写个简单的登录页,这个页面我是用thymeleaf模板写的,也是第一次使用thymeleaf,还请大家多多包涵这丑陋的画风。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Thymeleaf-Login-Demo</title>
</head>
<body>
<div id="header">
<h2>登录示例页面-供参考</h2>
<strong>demo login page for example</strong>
</div>
<div id="container">
<form th:action="@{/sign_in}" method="post">
<input name="username" type="text" placeholder="用户名"/>
<br/>
<input name="password" type="password" placeholder="密码"/>
<br/>
<input name="登录" type="submit" />
<br/>
</form>
</div>
</body>
</html>
项目启动起来,看到的页面效果图如下:
2.登录逻辑编写
结合数据库来实现用户登录,按照我们的思路,实现UserDetails, UserDetailsService 这两接口。首先,让我们自己的User实体类实现UserDetails接口.
2.1自定义user实体
自定义User实体类,这个类和我们的数据库结构是对应的。
/**
* @author developlee
* @since 2018/11/27 21:38
*/
@Entity
@Table(name = "tb_users")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
@Column(name = "age")
private String age;
@Column(name = "sex")
private String sex;
@Column(name = "isLock")
private boolean isLock;
@Column(name = "isEnabled")
private boolean isEnabled;
}
2.2 实现UserDetailsService接口
实现UserDetailsService接口,重写loadUserByUsername方法
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库查询用户
User user = userRepository.findByUsername(username);
if(user == null) {
throw new UsernameNotFoundException("用户" + username + "不存在");
}
return new MyUserDetails(user);
}
}
2.3 实现UserDetails接口
接下来,自定义MyUserDetails实现UserDetails接口,构造方法传入我们从数据库查询出来的User对象。
/**
* @author developlee
* @since 2018/11/27 21:42
*/
public class MyUserDetails implements UserDetails {
private User user;
public MyUserDetails(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return user.isLock();
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return user.isEnabled();
}
}
2.4 实现AuthenticationSuccessHandler
搞个登录成功的处理器MyLoginHandler,登录成功后,打印一行日志,并跳转到hello页
@Slf4j
@Component
public class MyLoginHandler implements AuthenticationSuccessHandler {
// 登录成功处理
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
log.info("登录成功!");
httpServletResponse.sendRedirect(httpServletRequest.getContextPath().concat("/hello"));
}
}
3.登录实战验证
登录试试看吧,见证奇迹的时刻
这是数据库中创建的用户
生成密码的代码
@Autowired
private PasswordEncoder passwordEncoder;
@Test
public void testMac() {
String password = "123456";
System.out.println("加密后密码:" + passwordEncoder.encode(password));
}
密码生成插入数据库,应该在用户注册时进行操作。
然后,让我们在hello页,新增一个logout按钮,来实现登出功能。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<p>Hello, World!</p>
<a id="logout-btn" th:href="@{/sign_out}">我要退出!</a>
</body>
</html>
点击‘我要退出!’即可跳转到登录页!
现在登录登出我们都已经准备就绪,接下来,为我们的登录加些料吧!
默认的登出链接是/logout,如果开启了CSRF验证(默认是开启的),则该登出请求,必须设置为post请求。登出后浏览器跳转路径模式/login?logout。SecurityContextLogoutHandler 默认是作为最后的logoutHandler的。在处理登出请求中,我们可以自己添加logoutHandler或者LogoutSuccessHandler的实现。
4.实现logoutHandler
接下来请看代码演示:
logoutHandler处理器
@Slf4j
@Component
public class MyLogoutHandler implements LogoutHandler {
@Override
public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
log.info("登出成功了!!!");
authentication.setAuthenticated(false); // 设置为未授权
}
}
5. 总结
Spring Security为我们提供在登录过程中各种场景的接口,以供我们自定义实现,处理自定义逻辑。使用Spring Security让登录更加规范和安全。
本文的所有代码我已经放在我的github.com上,感谢您的观看,如果有什么错误的地方,还请指出,共同探讨!
[…] 1.首先使用spring boot starter jpa 帮助我们通过实体类在数据库中简历对应的表结构,以及插入用户一条数据。 这里主要包含用户的账户、密码等信息,可以查看之前的文章,这里不再赘述。 精通SpringBoot: Spring Security 自定义认证流程 […]