系列目录
UserDetailsService实现类
在 security
包下新建一个service
包,新建 UserDetailsServiceImpl类,实现UserDetailsService接口:
@Service("UserDetailsServiceImpl")
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String s) {
User user = userService.findByUsername(s);
if (user == null) {
throw new UsernameNotFoundException("找不到该用户");
}
return null;
}
}
这里有一点,该方法的返回类型是 UserDetails,这是Spring Security内置的一个接口,顾名思义是包含用户信息的东西。这里也可以理解,虽然我们能通过自定义实现loadUserByUsername
方法,但是返回值怎么确定呢?不同的应用可能有不同的用户定义,那怎么才能适配到Spring Security中呢?UserDetails接口就是用来屏蔽具体的用户实现细节的。所以我们还需要实现一下这个接口。
UserDetails实现类
在 security
包下新建一个entity
包,新建 MyUserDetails类,实现UserDetails接口:
package cn.novalue.blog.security.entity;
import cn.novalue.blog.model.entity.User;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
@Data
public class MyUserDetails implements UserDetails {
private User user;
private Collection <? extends GrantedAuthority> authorities;
public MyUserDetails(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@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 true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
可以看到该接口包含很多方法,比如该用户所拥有的权限集合,账户是否锁定、是否过期、是否可用等等,我们可以根据自己的需求对权限做更加细粒度的控制,这里先不管那么多,全返回true
。
此外将我们自定义的User类以私有字段方式保存在该类中,并重写 getUsername
、getPassword
方法,让其返回User中的内容,权限集合暂时只设置个空集合。
修改 UserDetailsServiceImpl:
@Service("UserDetailsServiceImpl")
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String s) {
User user = userService.findByUsername(s);
if (user == null) {
throw new UsernameNotFoundException("找不到该用户");
}
MyUserDetails userDetails = new MyUserDetails(user);
// 先不设置权限信息
userDetails.setAuthorities(null);
return userDetails;
}
}
接下来只需要将我们编写的service配置到Spring Security即可。
Spring Security配置
修改之前SecurityConfig的代码:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("UserDetailsServiceImpl")
private UserDetailsService userDetailsService;
public SecurityConfig() {
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 注入自定义的service
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 禁用csrf
http.csrf().disable()
// 表单登录
.formLogin()
// 允许所有请求访问
.permitAll()
.and()
// 其他所有请求都需要认证
.authorizeRequests()
.anyRequest()
.authenticated();
}
测试登录
启动后端项目,访问localhost:8080/login
,会访问到Spring Security默认的登录页面
输入用户名密码:“zhangsan” “zhangsan”,提交。会返回到根目录:
这只是因为我们没有写页面,其实是登录成功的,可以对比输入错误的用户名密码,输出如下:
配置注销和记住我功能
到此我们看似实现了让Spring Security读取自己的数据库来做登录操作。我们主要做的仅仅是重写了一个方法(loadUserByUsername
),并对既有配置做了一些自定义设置。当然这并不完善,但是其它的操作也都与此类似。比如可以很简单地再配置上注销和记住我功能(其实就是我们常见的登录页面中的下次自动登录或者记住密码之类,勾选后登录状态会保持一段时间,不用每次访问页面都要登录):
@Override
protected void configure(HttpSecurity http) throws Exception {
// 禁用csrf
http.csrf().disable()
// 表单登录
.formLogin()
// 允许所有请求访问
.permitAll()
.and()
.logout()
.permitAll()
.and()
.rememberMe()
.and()
// 其他所有请求都需要认证
.authorizeRequests()
.anyRequest()
.authenticated();
}
这里同样可以按照前边的思路,去分析注销和记住我是怎么实现的,其实都是差不多的流程。
这里记住我功能是用的默认配置,也可以使用基于数据库的方式。下一篇就从源码上来看看该怎么修改记住我的配置。