基本实现
1、实现UserDetails接口
这个接口是spring security提供的核心用户信息。实现这个接口去定义用户的
username、password
他用户信息(昵称,手机号......)
权限信息
用户的管理信息(是否启用、是否过期)
public class UserDTO implements UserDetails {
private String username;
private String password;
private String mobile;
private List<RoleDTO> roleDTOS;
// 获取用户权限
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> list = new ArrayList<>();
for (RoleDTO roleDTO : roleDTOS) {
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(roleDTO.getRoleName());
list.add(simpleGrantedAuthority);
}
return list;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
public String getMobile() {
return this.mobile;
}
public List<RoleDTO> getRoleDTOS() {
return this.roleDTOS;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public void setRoleDTOS(List<RoleDTO> roleDTOS) {
this.roleDTOS = roleDTOS;
}
// 是否过期
@Override
public boolean isAccountNonExpired() {
return true;
}
// 是否锁定
@Override
public boolean isAccountNonLocked() {
return true;
}
// 用户密码是否过期
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 用户是否启用
@Override
public boolean isEnabled() {
return true;
}
}
2、实现UserDetailsService接口
这个接口是加载用户数据的核心接口。前面有说认证逻辑需要调用UserDetailsService的loadUserByUsername()方法查询用户信息。
@Service
public class UserServiceImpl implements UserDetailsService {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// TODO 根据username查库,这里先随便new了一个用户
UserDTO userDTO = new UserDTO();
userDTO.setUsername(username);
userDTO.setMobile("987654");
// 密码需要提供加密之后的密码
userDTO.setPassword(passwordEncoder.encode("123456"));
// 给用户添加了两个权限 ROLE_SYS ROLE_USER
List<RoleDTO> list = new ArrayList<>();
list.add(new RoleDTO("ROLE_SYS"));
list.add(new RoleDTO("ROLE_USER"));
userDTO.setRoleDTOS(list);
return userDTO;
}
}
3、Security配置
继承WebSecurityConfigurerAdapter类,对Security的安全策略、静态资源等进行配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserServiceImpl userService;
@Autowired
private CustomAccessDeniedHandler accessDeniedHandler;
@Autowired
private CustomFailHandler failHandler;
@Autowired
private CustomSuccessHandler successHandler;
/**
* 认证相关配置,AuthenticationManagerBuilder是用于构建AuthenticationManager的。
* AuthenticationManager中存有处理各种认证操作的AuthenticationProvider
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(getBCryptPasswordEncoder());
}
/**
* 配置静态资源
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/**");
}
/**
* 配置安全策略
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 禁用csrf
http.csrf().disable()
// 请求配置
.authorizeRequests()
// 配置请求权限 hasRole hasAuthority 两种方式差一个 ROLE_ 前缀
.antMatchers("/sys/**").hasAuthority("ROLE_SYS")
.antMatchers("/user/**").hasAuthority("ROLE_USER")
.antMatchers("/car/**").hasAuthority("ROLE_CAR")
// 配置接口不做任何控制
.antMatchers("/login/**").permitAll()
// 其他接口都需要进行认证
.anyRequest().authenticated()
.and().exceptionHandling()
// 认证失败的处理器
.accessDeniedHandler(accessDeniedHandler);
// 登录相关配置
http.formLogin().loginProcessingUrl("/login")
.usernameParameter("username").passwordParameter("password")
.defaultSuccessUrl("/default", true)
.failureHandler(failHandler)
.successHandler(successHandler);
// 注销相关配置
http.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.deleteCookies("remember-me")
.invalidateHttpSession(true);
// 出校成功的处理器
// .logoutSuccessHandler(logoutSuccessHandler);
// 记住我功能
http.rememberMe()
.tokenValiditySeconds(3600);
}
@Bean
BCryptPasswordEncoder getBCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
这里为 /sys/** /user/** /car/** 分别设置了 ROLE_SYS ROLE_USER ROLE_CAR 权限。用户拥有ROLE_SYS ROLE_USER 权限,所以访问/car/**时会走到认证失败处理器。
这里对于权限有两种概念 Authority 和 role。这两个更像是同一个东西的两个概念,role 比 Authority 多了一个 ROLE_ 前缀,区分两个可以在业务上做更好的区分。
public ExpressionInterceptUrlRegistry hasAuthority(String authority) {
return access(ExpressionUrlAuthorizationConfigurer.hasAuthority(authority));
}
private static String hasAuthority(String authority) {
return "hasAuthority('" + authority + "')";
}
public ExpressionInterceptUrlRegistry hasRole(String role) {
return access(ExpressionUrlAuthorizationConfigurer.hasRole(role));
}
private static String hasRole(String role) {
Assert.notNull(role, "role cannot be null");
Assert.isTrue(!role.startsWith("ROLE_"),
() -> "role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");
return "hasRole('ROLE_" + role + "')";
}
在权限对比中也都是调用了同一个方法做对比
@Override
public final boolean hasAuthority(String authority) {
return hasAnyAuthority(authority);
}
@Override
public final boolean hasAnyAuthority(String... authorities) {
return hasAnyAuthorityName(null, authorities);
}
@Override
public final boolean hasRole(String role) {
return hasAnyRole(role);
}
@Override
public final boolean hasAnyRole(String... roles) {
return hasAnyAuthorityName(this.defaultRolePrefix, roles);
}
private boolean hasAnyAuthorityName(String prefix, String... roles) {
Set<String> roleSet = getAuthoritySet();
for (String role : roles) {
String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
if (roleSet.contains(defaultedRole)) {
return true;
}
}
return false;
}
自定义登录方式
1、自定义过滤器
继承AuthenticationProvider。定义拦截的请求路径及请求方式,重写attemptAuthentication()方法调用对应的认证逻辑处理器
public class CustomAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private String mobileParameter = "mobile";
/**
* 是否仅 POST 方式
*/
private boolean postOnly = true;
public CustomAuthenticationFilter() {
super(new AntPathRequestMatcher("/login/mobile", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String mobile = obtainMobile(request);
if (mobile == null) {
mobile = "";
}
mobile = mobile.trim();
CustomAuthenticationToken authRequest = new CustomAuthenticationToken(mobile);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
protected String obtainMobile(HttpServletRequest request) {
return request.getParameter(mobileParameter);
}
protected void setDetails(HttpServletRequest request, CustomAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
public String getMobileParameter() {
return mobileParameter;
}
public void setMobileParameter(String mobileParameter) {
Assert.hasText(mobileParameter, "Mobile parameter must not be empty or null");
this.mobileParameter = mobileParameter;
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
}
2、自定义Token实体
继承AbstractAuthenticationToken。作为过滤器寻找对应认证处理器的媒介
public class CustomAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
/**
* 构建一个没有鉴权的 CustomAuthenticationToken
*/
public CustomAuthenticationToken(Object principal) {
super(null);
this.principal = principal;
setAuthenticated(false);
}
/**
* 构建拥有鉴权的 CustomAuthenticationToken
*/
public CustomAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
// must use super, as we override
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
3、自定义认证逻辑的处理器
实现AuthenticationProvider。定义authenticate()方法完成认证逻辑,定义supports()方法用于过滤器与对应处理器的匹配媒介
public class CustomAuthenticationProvider implements AuthenticationProvider {
private UserService userService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
CustomAuthenticationToken authenticationToken = (CustomAuthenticationToken) authentication;
String mobile = (String) authenticationToken.getPrincipal();
UserDetails userDetails = userService.loadUserByUsername(mobile);
// todo 校验,验证码、用户
// 校验成功,构建拥有鉴权的CustomAuthenticationToken返回
CustomAuthenticationToken authenticationResult = new CustomAuthenticationToken(userDetails, userDetails.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
// 判断 authentication 是不是 CustomAuthenticationToken 的子类或子接口,用于匹配过滤器和认证处理器
return CustomAuthenticationToken.class.isAssignableFrom(authentication);
}
public void setUserDetailsService(UserService userService) {
this.userService = userService;
}
}
4、修改配置
配置自定义的Provider与Filter
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
CustomAuthenticationProvider customAuthenticationProvider = new CustomAuthenticationProvider();
customAuthenticationProvider.setUserDetailsService(userService);
auth.authenticationProvider(customAuthenticationProvider);
auth.userDetailsService(userService).passwordEncoder(getBCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter();
customAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
customAuthenticationFilter.setAuthenticationSuccessHandler(successHandler);
customAuthenticationFilter.setAuthenticationFailureHandler(failHandler);
// 过滤器配置
http
.addFilterAfter(customAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
......
}
推荐文章:
Spring Security 中的 hasRole 和 hasAuthority 的区别https://cloud.tencent.com/developer/article/1703187