spring security 是基于【用户】-【角色】-【权限】机制,也就是说权限并不是直接分配给用户,而是分配给角色,再将相应的用户授权角色即可。
1、实现用户信息接口 UserDetails
UserDetails 接口定义了用户的基本信息,如用户名称、密码、账号是否过期、是否有效,以及所拥有的角色等:
package com.whowii.website4.security;
import java.io.Serializable;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
class UserInfo implements Serializable, UserDetails {
private static final long serialVersionUID = 1L;
private String username;
private String password;
private String role; // 拥有的角色列表(多个角色之间用逗号“,”分隔)
private boolean accountNonExpired; // 账号是否未过期
private boolean accountNonLocked; // 账号是否未锁定
private boolean credentialsNonExpired; // 账号凭证是否未过期
private boolean enabled; // 账号是否可用
public UserInfo(String username, String password, String role, boolean accountNonExpired, boolean accountNonLocked,
boolean credentialsNonExpired, boolean enabled) {
this.username = username;
this.password = password;
this.role = role;
this.accountNonExpired = true;
this.accountNonLocked = true;
this.credentialsNonExpired = true;
this.enabled = true;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return AuthorityUtils.commaSeparatedStringToAuthorityList(role);
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
}
2、实现获取用户信息的接口 UserDetailsService
package com.whowii.website4.security;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
@Component
public class MyUserDetailsService implements UserDetailsService {
// @Autowired
// private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("MyUserDetailsService.loadUserByUsername > readUser");
/*
理论上,我们使用 UserService 获取用户信息,判断账号是否过期、是否有效,以及角色信息等,这是为了描述方便,我使用变量代替:
1、分配 admin 用户,密码为 123,角色为 ROLE_ADMIN(必须在ROLE_开头,这是 spring security强制的)
2、分配 user 用户,密码为123,角色为 ROLE_USER
3、两个用户账号都未过期、未锁定、账号凭证未过期、账号可用
4、其他其他用户名登录时,返回 null 表示用户不存在
*/
if ("admin".equals(username)) {
return new UserInfo("admin", "123", "ROLE_ADMIN", true, true, true, true);
} else if ("user".equals(username)) {
return new UserInfo("user", "123", "ROLE_USER", true, true, true, true);
} else {
return null;
}
}
}
正如我在示例代码中所说,这里写成固定的变量只是为了方便演示,实际上可以为类注入 UserService(你自己定义的用户表服务层),以获取用户信息和角色信息。
3、实现用户信息验证器接口 AuthenticationProvider
package com.whowii.website4.security;
import java.util.Collection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
// 注入用户信息获取对象
@Autowired
private UserDetailsService userDetailService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
System.out.println("MyAuthenticationProvider.authenticate > :");
String userName = authentication.getName();// 这个获取表单输入中返回的用户名;
String password = (String) authentication.getCredentials();// 这个是表单中输入的密码;
UserInfo userInfo = (UserInfo) userDetailService.loadUserByUsername(userName); // 使用登录用户名获取用户信息
if (userInfo == null) {
System.out.println("----> 用户名不存在");
throw new BadCredentialsException("用户名不存在");
}
if (!password.equals(userInfo.getPassword())) {
throw new BadCredentialsException("密码不正确");
}
Collection<? extends GrantedAuthority> authorities = userInfo.getAuthorities();
return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
4、注册 AuthenticationProvider
在上一篇中我们使用了固定的两个用户,也就是SecurityConfig类中如下代码片段:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
System.out.println("SecurityConfig.configure > withUser");
auth.inMemoryAuthentication()
.passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("123")).roles("ADMIN")
.and()
.withUser("user").password(new BCryptPasswordEncoder().encode("123")).roles("USER");
}
这里,需要把这个方法删除,并注入MyAuthenticationProvider,完成后的SecurityConfig类如下:
package com.whowii.website4.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyAuthenticationProvider authProvider;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("SecurityConfig.configure > http");
http.csrf().disable().authorizeRequests().antMatchers("/manage/demo/hello").permitAll() // 此页面允许任何人访问,即使未登录
.antMatchers("/manage/demo/info1").hasAnyRole("ADMIN") // 仅允许 ADMIN 角色的用户访问
.antMatchers("/manage/demo/info2").hasAnyRole("USER") // 仅允许 USER 角色的用户访问
.and().formLogin().loginPage("/manage/demo/login") // 自定义登录页面
.failureUrl("/manage/demo/error") // 登录错误页面
.permitAll() // 允许任何用户访问
.and().logout().logoutUrl("/manage/demo/exit") // 退出登录
.logoutSuccessUrl("/manage/demo/index") // 退出登录成功返回的页面
.permitAll() // 也允许任务用户访问
.and().exceptionHandling();
}
}
5、测试结果
可以参考上一篇说明,测试各个页面,这里不细说。当然,也可以增加一些其他用户和角色。