关于Spring Security框架的依赖的参考代码为
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>2.3.5.RELEASE</version> </dependency>
1.使用自定义的用户名与密码
- UserDetailsServiceImpl 类,在类的声明之前添加 @Component 注解,实现UserDetailsService 接口,重写接口中的抽象方法
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
UserDetails userDetails = User.builder() .username("root") .password("1234") .authorities("稍后再解释……") .build();
return userDetails; } }
在 UserDetailsService 接口中的 loadUserByUsername() 方法是由Spring Security框架调用的,当用户在登录页面中输入用户名、密码之后,Spring Security就会使用用户提交的用户名来调用 loadUserByUsername() 方法,该方法应该返回与用户名匹配的用户信息,例如用户的密码等等,Spring Security会自动完成接下来的密码对比及授权等过程。
- 然后,在 cn.tedu.straw.portal.security 包下创建 WebSecurityConfigurer 类,在类的声明之前添加 @Configuration 解,继承自WebSecurityConfigurerAdapter 类,主要重写其中的 void config(AuthenticationManagerBuilder auth) 方法:
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsServiceImpl userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() { // 配置密码加密器 // Spring Security会自动将用户提交的密码使用这个密码加密器进行加密再对 比
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 配置身份验证
auth.userDetailsService(userDetailsService);
} }
登录流程
- 当用户在登录页面输入用户名、密码后,Spring Security将获取到这2个值;
- 由于自定义了 WebSecurityConfigurer 类,并配置了
auth.userDetailsService(userDetailsService); ,则Spring Security会使用用
户提交的用户名来调用 UserDetailsServiceImpl 中 loadUserByUsername() 方
法,并得到 UserDetails 类型的结果; - Spring Security会自动装配一个 PasswordEncoder 对象,并调用其方法对用户提交
的密码进行加密,与 UserDetails 对象中的密码进行对比验证。
3.密码加密器
Spring Security允许在密文的密码值之前添加算法ID,则不需要指定密码加密器,Spring Security也会根据算法ID自动使用相应的算法来验证密码!
private static PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
public static String encode(String password){
return "{bcrypt}" + passwordEncoder.encode(password);
}
4.关于授权访问
,可以在自定义的 WebSecurityConfigurer 类中重写 void configure(HttpSecurity http) 方法来配置:
http.authorizeRequests()
.antMatchers(urls).permitAll()
.anyRequest().authenticated();
// authorizeRequests() > 对请求进行授权
// antMatchers() > 设置某一些路径
// hasAuthority() > 需要具有某权限标识才可以访问,需在此前搭配 antMatchers()或其它用于设置路径的方法一起使用
// anyRequest() > 任意请求路径,如果此前通过其它API配置了路径,则表示“除了 以上路径以外的其它任意请求路径”
// authenticated() > 要求授权
Spring Security要求得到一个 UserDetails 类型的对象,然后自动完成身份验证和授权访问,但是, UserDetails 类型中可以封装的属性并不足以满足实际编程需求,例如连用户的ID和昵
称都无法封装进行,不便于后续的操作中识别用户的身份、获取常用信息,为了解决这个问题,应该自定义类,在原有的 UserDetails 实现类的基础之上进行扩展!在 cn.tedu.straw.portal.security 包下创建 LoginUserInfo 类,继承自Spring Security中 的 User 类(即 UserDetails 接口的实现类):
@Setter @Getter @ToString public class LoginUserInfo extends User { private Integer userId; private String nickName; public LoginUserInfo(String username, String password, Collection<? extends GrantedAuthority> authorities) { super(username, password, authorities); }public LoginUserInfo(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) { super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); } }
然后,修改 UserDetailsServiceImpl 类,通过用户名查询用户详情,并封装为以上定义的LoginUserInfo 类型返回给Spring Security框架
@Component public class UserDetailsServiceImpl implements UserDetailsService { @Autowired IUserService userService; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { // 调用业务层根据登录时填写的手机号查询数据库中的数据 UserLoginVO userLoginVO = userService.loginByPhone(s); // 如果手机号没有对应的数据,则直接返回null,登录将失败 if (userLoginVO == null) { return null; }// 从查询结果中取出权限数据,封装为Spring Security需要的类型 List<GrantedAuthority> authorities = new ArrayList<>(); List<PermissionVO> permissions = userLoginVO.getPermissions(); for (PermissionVO permission : permissions) {
String authority = permission.getAuthority(); GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authority); authorities.add(grantedAuthority); }// 创建LoginUserInfo对象,准备作为返回给Spring Security的对象 LoginUserInfo loginUserInfo = new LoginUserInfo( userLoginVO.getPhone(), userLoginVO.getPassword(), userLoginVO.getIsEnabled() == 1, true, true, userLoginVO.getIsLocked() == 0, authorities );loginUserInfo.setUserId(userLoginVO.getId()); loginUserInfo.setNickName(userLoginVO.getNickName()); // 返回 return loginUserInfo; } }
然后,在 WebSecurityConfigurer 中已经配置了使用以上 UserDetailsServiceImpl 的对象:
@Configuration public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter { @Autowired UserDetailsServiceImpl userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 配置身份验证 auth.userDetailsService(userDetailsService); }@Override protected void configure(HttpSecurity http) throws Exception { // 访问控制 // authorizeRequests():对请求进行验证 // antMatchers():设置请求路径 // permitAll():直接许可,即不需要验证 // anyRequest():除了此前配置过的URL以外的所有请求 // authenticated():必须已经验证 // formLogin():通过登录表单来验证用户登录 // csrf():跨域攻击 // disable():禁止 // 登录页面的URL String loginUrl = "/login.html"; // 处理登录表单的URL String loginProcessingUrl = "/login"; // 不需要登录即可请求的URL(白名单) String[] urls = { loginUrl, loginProcessingUrl, "/register.html"};http.authorizeRequests() .antMatchers(urls).permitAll() .anyRequest().authenticated(); http.formLogin() .loginPage(loginUrl) .loginProcessingUrl(loginProcessingUrl); http.csrf().disable(); } }
使用自定义页面
登录表单必须提交到 /login 位置;
用于输入用户名和密码的输入框的 name 必须是 username 和 password 。
全局获取用户登录信息
```bash
public R<Void> post(@Valid PostQuestionDTO postQuestionDTO, BindingResult bindingResult,
@AuthenticationPrincipal LoginUserInfo loginUserInfo){
if(bindingResult.hasErrors()){
String message = bindingResult.getFieldError().getDefaultMessage();
throw new InvalidParameterException(message);
}
}