【spring security】认证授权实现

基本实现

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

上一篇:安卓 ItemTouchHelper实现滑动删除和移动


下一篇:Oracle备份恢复:Rman Backup缓慢问题一例