Security能帮助我们快速实现认证功能,下面介绍几种常用的方式
基础类配置
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
}
不管哪种用户存储方式,都可以在这个方法配置实现
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
}
内存中实现
适用于用户数量较少,开发时间较短的项目
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("feng")
.password("fengPassword")
.authorities("ROLE_USER")
.and().withUser("name")
.password("namePassword")
.authorities("ROLE_MANAGER");
}
基于JDBC
@Autowired
private DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username, password, from users where username = ?")
.authoritiesByUsernameQuery("select username, authority from userauthorities where username = ?");
}
一般数据库中密码不会明文存储,所以需要加上密码转码器,这里使用的是bcrypt强哈希加密
@Autowired
private DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username, password, from users where username = ?")
.authoritiesByUsernameQuery("select username, authority from userauthorities where username = ?")
.passwordEncoder(new BCryptPasswordEncoder());
}
以LDAP作为存储
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userSearchBase("ou=perole") // 从名为people的组织单元搜索用户
.userSearchFilter("uid={0}")
.groupSearchBase("uid=groups") // 从名为groups的组织单元搜索组
.groupSearchFilter("member={0}")
.passwordCompare() //配置密码比对
.passwordAttribute("passcode") //声明密码属性
;
}
引用远程服务器
auth.ldapAuthentication()
.userSearchBase("ou=perole") // 从名为people的组织单元搜索用户
.userSearchFilter("uid={0}")
.groupSearchBase("uid=groups") // 从名为groups的组织单元搜索组
.groupSearchFilter("member={0}")
.passwordCompare() //配置密码比对
.passwordAttribute("passcode") //声明密码属性
.and()
.contextSource().url("ldap://xxx")
;
}
推荐方式
实际开发中,我们很可能是用微服务,把认证服务(auth)和用户服务(user)分开。
user服务存储了所有的用户信息,以及用户(加盐加密后)的密码
auth服务需要校验账号密码时,调用user服务的校验接口进行校验
WebSecurityConfigurerAdapter
@Autowired
private UserAuthProvider userAuthProvider;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(userAuthProvider);
}
实现 AuthenticationProvider 接口
public class UserAuthProvider implements AuthenticationProvider {
private static Logger logger = LoggerFactory.getLogger(UserAuthProvider.class);
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private UserDetailsService userDetailsService;
private UserDetailsServiceImpl iUserDetailsService;
public UserAuthProvider(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Autowired
private IUserService iUserService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = authentication.getName();
String password = (String) authentication.getCredentials();
// 调用微服务user的登录接口
LoginDTO loginDTO = new LoginDTO();
loginDTO.setLoginName(userName);
loginDTO.setPassword(password);
Object jsonObject = JSONObject.toJSON(iUserService.login(loginDTO).getData());
User user = (User) JSONObject.toJavaObject((JSON) jsonObject, User.class);
iUserDetailsService = new UserDetailsServiceImpl();
UserDetails userDetails = null;
if (user != null) {
userDetails = iUserDetailsService.loadUserByUserDTO(user, loginDTO.getPassword());
}
if (userDetails == null) {
logger.debug("Authentication failed: user doesn't exists!");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
if (userDetails != null) {
return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
} else {
logger.debug("Authentication failed: password does not match stored value!");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
这里的报错有个坑,修改message里面的内容没有起效果,原因是他每次都new一个新的,所以我们输入的字符串不生效。
自定义抛错
不使用BadCredentialsException,比如账号密码锁定,可以用LockedException,我们也可以自定义一个错误类
public class AuthUsernameNotFoundException extends AuthenticationException {
public AuthUsernameNotFoundException(String msg) {
super(msg);
}
public AuthUsernameNotFoundException(String msg, Throwable t) {
super(msg, t);
}
}