(一)、spring boot security 认证--自定义登录实现

版权声明:本文为博主原创文章,转载请附上博文链接,谢谢! https://blog.csdn.net/qq_30062125/article/details/86031593

简介

spring security主要分为两部分,认证(authentication)和授权(authority)。

这一篇主要是认证部分,它由 ProviderManager(AuthenticationManager)实现。具体层次结构如下:

AuthenticationManager说明

认证的核心就是登录,这里简单介绍下security自定义token登录的实现逻辑,同时兼容用户名密码登录。

大体分为以下几个步骤:

  1. 自定义AuthenticationToken实现: 不同登录方式使用不同的token
  2. 自定义AuthenticationProcessingFilter实现:用来过滤指定的登录方式,生成对应的自定义AuthenticationToken实现
  3. 自定义AuthenticationProvider实现:针对不同登录方式提供的认证逻辑
  4. 自定义UserDetailsService实现:自定义用户信息查询服务
  5. WebSecurityConfigurerAdapter声明:security信息配置,将前面的自定义对象注入到流程中。

代码路径

github代码路径

步骤说明

注:仅说明实现方式,逻辑简化处理。

1、自定义AuthenticationProcessingFilter实现

package demo.model;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;

/**
 *
 * @Description:  声明自定义token,是为后面的AuthenticationProvider提供支撑,区分不同类型的处理。
 *
 * @auther: csp
 * @date:  2019/1/7 下午6:25
 *
 */
public class LoginToken extends AbstractAuthenticationToken {

    private final String token;

    public LoginToken(String token) {
        super(null);
        this.token = token;
    }

    public LoginToken(String token, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.token = token;
        setAuthenticated(true);
    }


    // 这个地方传递下token,逻辑是简化的逻辑,具体可以根据实际场景处理。
    // 如jwt token,解析出来username等信息,放到该token中。
    @Override
    public Object getCredentials() {
        return this.token;
    }

    @Override
    public Object getPrincipal() {
        return null;
    }
}

2、自定义AuthenticationProcessingFilter实现

package demo.filter;

import demo.model.LoginToken;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @Description: 自定义filter,用来筛选出来想要的登录方式。
 *
 * @auther: csp
 * @date:  2019/1/7 下午6:27
 *
 */
public class MyTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private static final String SPRING_SECURITY_RESTFUL_TOKEN = "token";

    public static final String SPRING_SECURITY_RESTFUL_LOGIN_URL = "/tokenLogin";
    private boolean postOnly = true;

    // 请求路径声明,url不能被权限拦截。
    // 会根据AntPathRequestMatcher 筛选请求,符合条件的才会认为有效
    public MyTokenAuthenticationFilter() {
        super(new AntPathRequestMatcher(SPRING_SECURITY_RESTFUL_LOGIN_URL, null));
    }


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        AbstractAuthenticationToken authRequest;

        String token = obtainParameter(request, SPRING_SECURITY_RESTFUL_TOKEN);

        authRequest = new LoginToken(token);

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);

        // 根据AuthenticationManager校验具体的请求,实际的登录验证触发。
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    private void setDetails(HttpServletRequest request,
                            AbstractAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

    private String obtainParameter(HttpServletRequest request, String parameter) {
        String result =  request.getParameter(parameter);
        return result == null ? "" : result;
    }
}

3、自定义AuthenticationProvider实现

package demo.provider;

import demo.model.LoginToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;

/**
 *
 * @Description: token验证逻辑
 *
 * @auther: csp
 * @date:  2019/1/7 下午9:05
 *
 */
public class MyTokenProvider implements AuthenticationProvider {

    UserDetailsService userDetailsService;

    public MyTokenProvider(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }


    @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String token = (authentication.getCredentials() == null) ? "NONE_PROVIDED"
            : (String) authentication.getCredentials();

        // loginToken_user
        // 这个地方简化处理,实际需要校验token,如jwt token 需要解密 验证信息
        if (token.startsWith("loginToken_")) {

            // 验证下token对不对,然后加载下信息。
            String userName = token.split("_")[1];
            UserDetails user = userDetailsService.loadUserByUsername(userName);

            LoginToken result = new LoginToken(token, user.getAuthorities());
            result.setDetails(authentication.getDetails());

            return result;
        }

        throw new BadCredentialsException("token无效");
    }

    /**
     *
     * @Description:  只处理特定类型的登录
     *
     * @auther: csp
     * @date:  2019/1/7 下午9:03
     * @param authenticationClass
     * @return: boolean
     *
     */
    @Override
    public boolean supports(Class<?> authenticationClass) {
        return (LoginToken.class
                .isAssignableFrom(authenticationClass));
    }

    public UserDetailsService getUserDetailsService() {
        return userDetailsService;
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
}

4、自定义UserDetailsService实现

package demo.service;

import demo.model.UrlGrantedAuthority;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * @Description: 用户信息查询逻辑,这里token认证和用户名登录使用同一个service
 *
 * @auther: csp
 * @date:  2019/1/7 下午9:06
 *
 */
@Component public class MyUserDetailsService implements UserDetailsService {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        logger.info("用户的用户名: {}", username);

        List<GrantedAuthority> list = new ArrayList<GrantedAuthority>();


        // 模拟下逻辑,简单处理下。
        if ("admin".equals(username)) {
            // 自定义权限实现
            UrlGrantedAuthority authority = new UrlGrantedAuthority(null, "/admin/index");
            list.add(authority);
            // 封装用户信息,并返回。参数分别是:用户名,密码,用户权限
            User user = new User(username, "123456", list);

            return user;
        }
        else if ("user".equals(username)) {
            list.add(new SimpleGrantedAuthority("ROLE_USER"));
            User user = new User(username, "123456", list);

            return user;
        }
        else {
            throw new DisabledException("用户不存在");
        }

    }
}

5、WebSecurityConfigurerAdapter声明


package demo.config;

import demo.filter.MyTokenAuthenticationFilter;
import demo.provider.MyTokenProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.vote.AbstractAccessDecisionManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private UserDetailsService myUserDetailsService;

	// @formatter:off
	@Override
	protected void configure(HttpSecurity http) throws Exception {

		http
			// 将tokenfilter追加进去,筛选出来tokenLogin逻辑。
			.addFilterBefore(getTokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
			.logout().logoutUrl("/logout").logoutSuccessUrl("/").and()
			.formLogin().loginPage("/login").defaultSuccessUrl("/").failureUrl("/login-error").permitAll().and()
			.authorizeRequests()
			.antMatchers(MyTokenAuthenticationFilter.SPRING_SECURITY_RESTFUL_LOGIN_URL).permitAll()
			.antMatchers("/admin/**").hasRole("ADMIN")
			.antMatchers("/user/**").hasRole("USER")
			.anyRequest().authenticated();
	}
	// @formatter:on


	@Override
	public void configure(WebSecurity web) throws Exception {
		//忽略请求 不走security filters
		web.ignoring().antMatchers("/login-error2","/css/**","/info","/health","/hystrix.stream");
	}


	/**
	 * 1、用户验证,指定多个AuthenticationProvider
	 * 实际执行时候根据provider的supports方法判断是否走逻辑
	 *
	 * 2、如果不覆盖,优先会获取AuthenticationProvider bean作为provider;
	 * 如果没有bean,默认提供DaoAuthenticationProvider
	 *
	 * @param auth
	 */
	@Override
	public void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.authenticationProvider(myTokenProvider());
		// 未配置时候用户名密码默认登录provider
		auth.authenticationProvider(daoAuthenticationProvider());
	}

	@Bean
	public DaoAuthenticationProvider daoAuthenticationProvider(){
		DaoAuthenticationProvider provider1 = new DaoAuthenticationProvider();
		// 设置userDetailsService
		provider1.setUserDetailsService(myUserDetailsService);
		// 禁止隐藏用户未找到异常
		provider1.setHideUserNotFoundExceptions(false);
		// 使用BCrypt进行密码的hash
//		provider1.setPasswordEncoder(myEncoder());
		return provider1;
	}


	/**
	 *
	 * @Description:  自定义token方式认证逻辑provider
	 *
	 * @auther: csp
	 * @date:  2019/1/7 下午9:18
	 * @return: demo.provider.MyTokenProvider
	 *
	 */
	@Bean
	public MyTokenProvider myTokenProvider() {
		return new MyTokenProvider(myUserDetailsService);
	}

//	@Bean
	public BCryptPasswordEncoder myEncoder(){
		return new BCryptPasswordEncoder(6);
	}

	/**
	 * token登录过滤器,用来筛选出来token登录方式。
	 */
	@Bean
	public MyTokenAuthenticationFilter getTokenAuthenticationFilter() {
		MyTokenAuthenticationFilter filter = new MyTokenAuthenticationFilter();
		try {
			// 使用的是默认的authenticationManager
			filter.setAuthenticationManager(this.authenticationManagerBean());
		} catch (Exception e) {
			e.printStackTrace();
		}
//		filter.setAuthenticationSuccessHandler(new MyLoginAuthSuccessHandler());
		filter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/"));
		filter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/login-error2"));
		return filter;
	}

}

6、验证

  1. 用户名密码登录:

http://127.0.0.1:9999/

admin 123456

user 123456

  1. token登录:

user登录:
http://127.0.0.1:9999/tokenLogin?token=loginToken_user

admin登录:
http://127.0.0.1:9999/tokenLogin?token=loginToken_admin

猜你喜欢

转载自blog.csdn.net/qq_30062125/article/details/86031593