1. 程式人生 > 其它 >【spring security】認證授權實現

【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