图1 思惟导图css
当咱们在项目中引入 Spring Security 的相关依赖后,默认的就是表单登陆形式;俗话说:“听人劝,吃饱饭”,既然 Spring Security 已经给咱们安排的明明白白了,咱们就从表单登陆开始吧。html
在开始以前,咱们能够站在 Spring Security 的角度上思考:若是我本身来实现表单登陆的功能,那么我须要作哪些工做呢?前端
就我我的而言,我可能会考虑如下几点:spring
能够简单的制做成以下流程图:数据库
图1-1 表单登陆简单流程图json
上方属于咱们本身设想的实现方案,属于"低配版"模式,下面咱们来看看 Spring Security 是怎么作的。Spring Security的思路和咱们大同小异,优势在于其提供了很好的封装,提升了框架自己的可扩展性。安全
Spring Security 的实现步骤以下:app
制做成流程图如示:框架
图1-2 Spring Security表单登陆认证流程图ide
这时你可能会一脸懵逼:这咋和刚刚咱们本身设想的彻底不同呀~ 又是Manager又是Provider的;莫慌,且听我慢慢道来。
上面出现了不少新的概念,咱们目前不须要十分细致的了解它们是怎么发挥做用的,只须要大概知道它们有什么用的便可;具体的介绍会在下篇《认证(二):表单登陆认证流程源码解析》娓娓道来。
通过上述的原理探讨,咱们大致上能弄懂了整个表单登陆有哪几个模块须要处理;可简单的总结为3个模块:
俗话说:“光说不练假把式”,那么就让咱们来实战一番吧。
做为一个Java Web项目,第一步固然是引入相关依赖;直接引入Spring Boot封装好的starter便可。
Spring Security 提供了UserDetails接口,用于获取用户的基本信息(帐号密码、权限集合、是否锁定等等),咱们只须要根据自身的业务场景,实现该接口便可。
Spring Security提供的UserDetails.class接口
自定义业务相关的用户信息类,业务定义的UserInfo.class必须带有username和password相关的信息,用于作用户验证;项目根据自身需求来判断是否须要使用下面的几个boolean方法,若是无相关需求则直接返回true便可。
@Setter
public class UserInfo implements UserDetails {
private String username;
private String password;
/**
* UserDetails的接口
* 用户权限集,默认须要添加ROLE_做为前缀
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>(1);
simpleGrantedAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return simpleGrantedAuthorities;
}
/**
* 获取用户密码
*/
@Override
public String getPassword() {
return this.password;
}
/**
* 获取用户名
*/
@Override
public String getUsername() {
return this.username;
}
/**
* 帐户是否未过时 --true则为未过时
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 帐户是否未被锁定
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 帐户凭证是否未过时
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 帐户是否可用
*/
@Override
public boolean isEnabled() {
return true;
}
}
在定义完用户实体UserInfo后,咱们同时也须要提供对应的Service层的API方法,用以进行一些基本的操做,诸如:新增用户、删除用户等。
Spring Security 也提供了对应的Service层接口,UserDetailsService,接口只有一个方法:UserDetails loadUserByUsername(String username);根据用户名加载用户信息.
UserDetailsService.class
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
所以咱们能够自定义业务相关的UserInfoServiceImpl类,实现Spring Security提供的 UserDetailsService接口
UserInfoServiceImpl.class
/**
* 用户信息service模块
*
* UserDetailsService接口为SpringSecurity内置接口,内部有方法:
* UserDetails loadUserByUsername(String username):如名所得 根据用户名加载用户
* 该方法主要是在:DaoAuthenticationProvider中被调用,获取用户的信息
*
* @author 小奇
*/
@Slf4j
@Service
public class UserInfoServiceImpl implements UserDetailsService, UserInfoService {
private final UserInfoDAO userInfoDAO;
@Autowired
public UserInfoServiceImpl(UserInfoDAO userInfoDAO) {
this.userInfoDAO = userInfoDAO;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<UserInfo> userInfoOpt = Optional.ofNullable(userInfoDAO.loadUserByUsername(username));
UserInfo user = userInfoOpt.orElseThrow(() -> new UsernameNotFoundException("can't not load user by username"));
log.info("根据用户名:{}查询用户成功", user.getUsername());
return user;
}
}
众所周知,密码是不能以明文的方式存储的,贴心的Spring Security天然不会忘记提供加密的功能。PasswordEncoder接口,主要提供2个方法;String encode(CharSequence rawPassword)方法用于加密,由咱们在注册用户的时候调用;boolean matches(CharSequence rawPassword, String encodedPassword) 方法用于匹配,登陆验证时由Spring Security框架调用。
PasswordEncoder.class
若是项目有本身的加解密方式,只须要实现该接口便可,若是没有能够尝试使用Spring提供的BCryptPasswordEncoder密码加密器。
在这一块上,咱们能够自定义与自身业务有关的登陆逻辑判断,目前没有这种需求就使用Spring Security提供的默认实现便可。
登陆的后置处理分两种状况,第一种是登陆成功的处理,一种是登陆失败的处理。
Spring Security提供了认证成功处理器接口AuthenticationSuccessHandler,当咱们有一些自定义的业务逻辑,诸如:用户登陆成功后赠送积分,或者登陆成功后自动跳转……就能够经过提供该接口的自定义实现。
AuthenticationSuccessHandler.class
public interface AuthenticationSuccessHandler {
/**
* 默认方法
*/
default void onAuthenticationSuccess(HttpServletRequest request,HttpServletResponse response, FilterChain chain, Authentication authentication)
throws IOException, ServletException{
onAuthenticationSuccess(request, response, authentication);
chain.doFilter(request, response);
}
/**
* 成功后会被调用
*/
void onAuthenticationSuccess(HttpServletRequest request,HttpServletResponse response, Authentication authentication)
throws IOException, ServletException;
}
自定义成功处理器 WebAuthenticationSuccessHandler.class
/**
* 自定义验证成功处理器
* @author 小奇
*/
@Slf4j
@Component
public class WebAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
log.info("登陆成功~~");
// 返回json 可添加自身业务逻辑 如:登陆成功后添加用户积分等……
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}
AuthenticationFailureHandler失败处理器和成功处理器相似,不作过多的解析,上代码。
public interface AuthenticationFailureHandler {
/**
* 失败后调用
*/
void onAuthenticationFailure(HttpServletRequest request,HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException;
}
自定义失败处理器WebAuthenticationFailureHandler.class
/**
* 自定义验证失败处理器
* @author 小奇
*/
@Slf4j
@Component
public class WebAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
log.error("登陆失败");
// 把exception返回给前台
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(exception));
// 可作其余业务逻辑,诸如限制天天登陆失败的次数
}
}
还记得以前咱们提过的Spring Security为人广为诟病的繁琐配置吗?自从搭上Spring Boot的列车以后,有了翻天覆地的改变。
下面就来简单配置一下咱们在上面自定义的一些模块吧。
/**
* @author kylin
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private WebAuthenticationSuccessHandler successHandler;
@Autowired
private WebAuthenticationFailureHandler failureHandler;
/**
* 密码加密器,使用spring提供的BCryptPasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* http请求安全配置
*
* @param http
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/", "/css/", "/about", "/test").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.successHandler(successHandler)
.failureHandler(failureHandler)
.permitAll()
.and()
.csrf().disable();
}
}
整个配置就基本完成了,也比较简单易懂;对一些配置进行基础的讲解
以上内容则为本文的全内容,文章经过原理探讨、动手尝试逐一展开。若有错误之处,请多多指正