本案例基于springboot2.0.4,只说对登录验证的管理,不涉及权限的管理。因为学习新东西一下子太多,很容易乱。
首先添加maven依赖,直接开启springboot自带的spring security即可。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
接下来配置config,需要继承WebSecurityConfigurerAdapter
@SpringBootConfiguration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowired
private MyAuthenticationFailHandler myAuthenticationFailHandler;
@Bean
UserDetailsService customUserService() {
return new MyCustomUserService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 使用自定义UserDetailsService
auth.userDetailsService(customUserService()).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginProcessingUrl("/user/login")
// 自定义的登录验证成功或失败后的去向
.successHandler(myAuthenticationSuccessHandler).failureHandler(myAuthenticationFailHandler)
// 禁用csrf防御机制(跨域请求伪造),这么做在测试和开发会比较方便。
.and().csrf().disable();
}
}
如果验证数据是从数据库中获取的话还需要添加实现了UserDetailsService接口的类,操作数据使用的是mybatis,代码就不罗列了。
@Component
public class MyCustomUserService implements UserDetailsService {
@Autowired
private SysUserMapper sysUserMapper;
/** * 登陆验证时,通过username获取用户的所有权限信息 * 并返回UserDetails放到spring的全局缓存SecurityContextHolder中,以供授权器使用 */
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
MyUserDetails myUserDetails = new MyUserDetails();
myUserDetails.setUsername(username);
SysUser sysUser = sysUserMapper.selectByKeyword(username);
myUserDetails.setPassword(sysUser.getPassword());
return myUserDetails;
}
}
上面代码中的MyUserDetails是实现了UserDetails接口,自定义了返回对象。如果不使用MyUserDetails也可以,可以使用默认的User类,只不过验证就复杂点了,为了简化一些不必要的验证属性就自定义了一个UserDetails
下面是User的代码的一些属性。(是不是挺杂乱的对于初学者)
private static final long serialVersionUID = 500L;
private static final Log logger = LogFactory.getLog(User.class);
private String password;
private final String username;
private final Set authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
这是自定义的MyUserDetails类,消除了一些不需要的属性对业务的影响。(只保留了3个属性)
@Component
public class MyUserDetails implements UserDetails {
private String username;
private String password;
private Collection extends GrantedAuthority> authorities;
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setAuthorities(Collection extends GrantedAuthority> authorities) {
this.authorities = authorities;
}
@Override
public Collection extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
还有两个登陆后去向类,这两个类就随意写了,根据业务需求来写即可。其实spring security本身也已经实现了默认的登录失败和登录成功去向,但是在前后端分离下,后端并不负责页面的跳转或者错误提示等。后端也会返回带有登录信息的Json格式的数据给前端,所以要想满足这些需求就需要实现AuthenticationFailureHandler和AuthenticationSuccessHandler接口。值得注意的是MyAuthenticationSuccessHandler类中的onAuthenticationFailure方法可以打印异常(错误)信息。
@Component
public class MyAuthenticationFailHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
// 允许跨域
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
// 允许自定义请求头token(允许head跨域)
httpServletResponse.setHeader("Access-Control-Allow-Headers", "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
httpServletResponse.getWriter().write(200);
}
}
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
// 允许跨域
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
// 允许自定义请求头token(允许head跨域)
httpServletResponse.setHeader("Access-Control-Allow-Headers", "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
httpServletResponse.getWriter().write(e.getMessage());
}
}
接下来就是前端的登录代码,该action需要和WebSecurityConfiguration类中loginProcessingUrl(“/user/login”)的值一致。当然了下面的代码也可以用ajax来替代,不一定要用from表单登录。
<form action='/user/login' method='POST'>
<table>
<tr>
<td>User:td>
<td><input type='text' name='username' value=''>td>
tr>
<tr>
<td>Password:td>
<td><input type='password' name='password'/>td>
tr>
table>
form>
以上就是简单的spring security在前后端分离下的运用例子。不过光是这么写,不明白的人看了还是一头雾水,也不能充分展示spring security的强大。
--------------------------------------
重点再说下WebSecurityConfiguration类
在WebSecurityConfiguration类中需要把自定义的MyAuthenticationSuccessHandler和MyAuthenticationFailHandler去向类注入,并在loginProcessingUrl(“/user/login”)后面激活这两个类的使用
@Autowired
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowired
private MyAuthenticationFailHandler myAuthenticationFailHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().loginProcessingUrl("/user/login")
// 自定义的登录验证成功或失败后的去向
.successHandler(myAuthenticationSuccessHandler).failureHandler(myAuthenticationFailHandler)
// 禁用csrf防御机制(跨域请求伪造),这么做在测试和开发会比较方便。
.and().csrf().disable();
}
自定义MyUserDetailsService类也需要在WebSecurityConfiguration类中注入和激活使用
@Bean
UserDetailsService customUserService() {
return new MyCustomUserService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 使用自定义UserDetailsService
auth.userDetailsService(customUserService()).passwordEncoder(new BCryptPasswordEncoder());
}
上面的代码有个.passwordEncoder(new BCryptPasswordEncoder())这个意思就是指定密码加密的方式。这里采用的是BCrypt加密方式。换句话说,当spring security获取到你前端传过来的密码时,就自动用BCrypt加密密码,然后拿去和数据库密码匹对。所以在数据库里存的密码也得是用BCrypt加密后的密码。
如果这么说,是不是也可以指定自定义加密方式呢。是可以的,实现也很简单,只要实现PasswordEncoder接口即可。在把.passwordEncoder(new BCryptPasswordEncoder())更改为.passwordEncoder(new MyPasswordEncoder())即可。
@Component
public class MyPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
return SecurityUtil.encrypt(charSequence.toString());
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return encode(charSequence).equals(s);
}
}
接下来说下这个
http.formLogin().loginProcessingUrl("/user/login")
// 自定义的登录验证成功或失败后的去向
.successHandler(myAuthenticationSuccessHandler).failureHandler(myAuthenticationFailHandler)
// 禁用csrf防御机制(跨域请求伪造),这么做在测试和开发会比较方便。
.and().csrf().disable();
关于这个一般来写的话都是有http.forLogin().loginPage(“/login”)啥的,这个是自定义登录页面,默认为/login。说白了就是你访问spring security保护的请求时候如果发现你没有登录,就会跳转到指定页面,使用默认或者不写的话spring security就会跳转到已经帮你写好了一个默认登录页面了。在前后端分离下,这个就不管了。loginProcessingUrl(“/user/login”)就是登录请求。
光是看上面的似乎有点囫囵吞枣,建议在看另一篇博文,主要分析登录验证的过程和一些源码的浅析。(写的不咋滴,但是认真看完后我觉得应该会有所收获)
SpringSecurity登录验证步骤和源码浅析