美文网首页
SpringSecurity

SpringSecurity

作者: dwwl | 来源:发表于2019-10-25 17:15 被阅读0次

SpringSecurity

github:https://github.com/Fly0708/securityDemo.git
一、框架的基础使用

当pom文件中加入Spring Security依赖时,默认开启权限认证

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

自定义SecurityConfig配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private SuccessHandler successHandler;

    @Autowired
    private FailureHandler failureHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            //basic和表单两种方式,formLogin的拦截器UsernamePasswordAuthenticationFilter
                .formLogin()
                .loginPage("/login")
            //自定义登录url(覆盖AbstractAuthenticationProcessingFilter中的RequestMatcher),post方式,
                .loginProcessingUrl("/login")
            //覆盖默认成功处理器,默认实现跳转上一url,先返回登录用户信息json
                .successHandler(successHandler)
            //覆盖默认失败处理器,默认实现跳转登录页面loginPage,先返回失败信息
                .failureHandler(failureHandler)
                .and()
            //对授权的路径进行配置,
                .authorizeRequests()
                .antMatchers("/login.html","/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable();
    }

    //spring security内置的加密器,加密中的字符串中有salt,每次加密的salt均不一样,所以对同一内容加密后的结果是不同的。
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

SuccessHandler.java

@Component
public class SuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        ObjectMapper objectMapper = new ObjectMapper();

        response.getWriter().write(objectMapper.writeValueAsString(authentication));
    }
}

FailureHandler.java

@Component
public class FailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(500);
        ObjectMapper objectMapper = new ObjectMapper();
        String content = objectMapper.writeValueAsString(exception.getMessage());
        response.getWriter().write(content);
    }
}

UserService.java

//为方便,模拟从数据库查询用户信息
@Service
public class UserService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Set<GrantedAuthority> authorities = new HashSet<>();

        authorities.add(new SimpleGrantedAuthority("admin"));
        return new User("fly",passwordEncoder.encode("123"),authorities);
    }
}

效果:

访问http://localhost:8080/test,返回{"result":"0","message":"请登录","data":null}

访问http://localhost:8080/login.html,输入错误用户名或密码,返回{"result":"0","message":"用户名或密码错误","data":null}//具体错误信息可通过覆盖DaoAuthenticationProvider或者在UserDetailService中添加逻辑代码实现。输入正确用户名 密码,返回

{
    "authorities": [{
        "authority": "admin"
    }],
    "details": {
        "remoteAddress": "0:0:0:0:0:0:0:1",
        "sessionId": "21385DFFE1DC1DBEE43CDB6F8FD00444"
    },
    "authenticated": true,
    "principal": {
        "password": null,
        "username": "fly",
        "authorities": [{
            "authority": "admin"
        }],
        "accountNonExpired": true,
        "accountNonLocked": true,
        "credentialsNonExpired": true,
        "enabled": true
    },
    "credentials": null,
    "name": "fly"
}

自定义格式在SuccessHandler中实现。

过程分析

一张经典的SpringSecurity执行图

SpringSecurity的执行是依赖一套FilterChain来实现的

SecurityContextPersistenceFilter是第二个Filter,通过断点方式来看这条Filter Chain

1571972343156.png

比较重要的几个Filter:

SecurityContextPersistenceFilter:在每次请求处理之前将该请求相关的安全上下文信息加载到SecurityContextHolder中,用于FilterSecurityInterceptor判断是否已经认证,SecurityContextHolder中存储。
ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();SecurityContextHolder中的SecurityContext是线程级变量

UsernamePasswordAuthenticationFilter:拦截请求(看其构造函数中配置)。这里采用了模板的方式,具体拦截过程在AbstractAuthenticationProcessingFilter中的doFilter方法。

FilterSecurityInterceptor:根据配置,即那些请求需要permitAll ,哪些请求需要authenticated。

主要的验证逻辑是在UsernamePasswordAuthenticationFilter中实现的

org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#UsernamePasswordAuthenticationFilter(构造函数)


    public UsernamePasswordAuthenticationFilter() {
    //这个类继承了一个抽象类,这里的构造函数设置了父类的requiresAuthenticationRequestMatcher属性(在判断是否拦截请求的时候用到)
        super(new AntPathRequestMatcher("/login", "POST"));
    }

org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#doFilter

        ...
        //用上面的requiresAuthenticationRequestMatcher进行匹配判断,即拦截路径/login POST请求
        if (!requiresAuthentication(request, response)) {
            chain.doFilter(request, response);

            return;
        }
        ...
        
        try {
        //尝试进行验证 子类实现(关键方法)
            authResult = attemptAuthentication(request, response);
            if (authResult == null) {
                // return immediately as subclass has indicated that it hasn't completed
                // authentication
                return;
            }
            sessionStrategy.onAuthentication(authResult, request, response);
        }
        catch{
        ...
        }
        ...
        //调用登录成功处理器
        successfulAuthentication(request, response, chain, authResult);

org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#attemptAuthentication

    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 username = obtainUsername(request);
        String password = obtainPassword(request);

        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();

        //封装用于验证的token
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                username, password);

        // Allow subclasses to set the "details" property sessionID,封装请求的一些信息
        setDetails(request, authRequest);

        //得到AuthenticationManager对象(全局唯一)的实现类ProviderManager进行登录验证
        return this.getAuthenticationManager().authenticate(authRequest);
    }

​ org.springframework.security.authentication.ProviderManager#authenticate

...
//通过循环所注册的provider,通过provider的support(token)方法查找能处理该类型token的provider
for (AuthenticationProvider provider : getProviders()) {
    if (!provider.supports(toTest)) {
                continue;
            }
}
try {
            //支持UsernamePasswordAuthenticationToken的是DaoAuthenticationProvider
                result = provider.authenticate(authentication);

                if (result != null) {
                    copyDetails(authentication, result);
                    break;
                }
            }

org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider#authenticate

preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user),

org.springframework.security.authentication.dao.DaoAuthenticationProvider#retrieveUser

//利用userDetailService查找用户,security自动配置
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;

org.springframework.security.authentication.dao.DaoAuthenticationProvider#additionalAuthenticationChecks

...
//这个方法对密码进行了校验
    String presentedPassword = authentication.getCredentials().toString();

        if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            logger.debug("Authentication failed: password does not match stored value");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }

整体的流程是这样的,

一张经典的图

1571974749801.png

所以可以通过添加自定义filter provider token可以实现自定义登录流程,

相关文章

网友评论

      本文标题:SpringSecurity

      本文链接:https://www.haomeiwen.com/subject/lccevctx.html