zoukankan      html  css  js  c++  java
  • spring security 学习二

    doc:https://docs.spring.io/spring-security/site/docs/

    基于表单的认证(个性化认证流程):

    一、自定义登录页面

    1、在securityConfigy配置类中的config方法中添加调用链方法

        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            http.formLogin()//指定是表单登录
                    .loginPage("/cus_login.html")//登录页面
                    .and()
                    .authorizeRequests()//授权
                    .anyRequest()//任何请求
                    .authenticated();//都需要身份认证
        }

    2、同时在resources/resources下创建一个html文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    自定义登录页面
    </body>
    </html>

    /3、配置好之后,启动项目,访问localhost:9999/h  api

    浏览器后报错:,重定向到登录页面次数太多

    4、为什么出现这种情况:

      因为在securityconfigy配置类中指定了一个loginPage,但是在方法链中,有表明:任何请求都需要认证

    .anyRequest()//任何请求
                    .authenticated();//都需要身份认证

    处理:在anyRequest方法前和authorizeRequests方法后添加antMatchers匹配规则,指定登录页面允许访问

    .authorizeRequests()//授权
                    .antMatchers("/cus_login.html").permitAll()

    配置完成后再次访问localhost:9999/h  :,即可跳转至自定义的登录页面

    5、完善登录页面

    //在usernamePasswordAuthenticationFilter中处理的action为/login,请求方式是post   
    public UsernamePasswordAuthenticationFilter() { super(new AntPathRequestMatcher("/login", "POST")); }
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    自定义登录页面
    <form action="/authentication/form" method="POST">
        <table>
            <tr>
                <td>用户名:</td>
                <td><input type="text" name="username"></td>
            </tr>
            <tr>
                <td>密码:</td>
                <td><input type="text" name="password"></td>
            </tr>
            <tr>
                <td>
                    <button type="submit">登录</button>
                </td>
            </tr>
        </table>
    </form>
    </body>
    </html>

     因为在登录页面中设定的action为“/authentication/form”,所以在securityConfig配置类的方法链中添加loginProcessingUrl方法

        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            http.formLogin()//指定是表单登录
                    .loginPage("/cus_login.html")//登录页面
                    .loginProcessingUrl("/authentication/form")
                    .and()
                    .authorizeRequests()//授权
                    .antMatchers("/cus_login.html").permitAll()
                    .anyRequest()//任何请求
                    .authenticated();//都需要身份认证
        }

    重启项目:访问localhost:9999/h

    显示为自定义的页面。

    csrf().disable();//跨站防护不适用

    6、将loginPage(/cus_login.html) html页面改为一个controller代码

    ①新建一个controller

    @RestController
    public class LoginSecurityController {
    
        @RequestMapping("/authentication/require")
        public String requireAuthentication(HttpServletRequest request, HttpServletResponse response) {
            
    
            return null;
        }
    }

    ②修改securityconfig配置类中的config中的方法调用链中的loginPage

        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            http.formLogin()//指定是表单登录
                    .loginPage("/authentication/require")//登录页面
                    .loginProcessingUrl("/authentication/form")
                    .and()
                    .authorizeRequests()//授权
                    .antMatchers("/authentication/require").permitAll()
                    .anyRequest()//任何请求
                    .authenticated()//都需要身份认证
                    .and()
                    .csrf().disable();//跨站防护不适用
        }

    二、自定义登录成功的处理

     实现AuthenticationSucessHandler接口,一下实现方式是:继承SaveRequestAwareAuthenticationSuccessHandler类(这样的话是可以根据请求方式的类型,来返回不同个数的数据,json或者默认)

    package com.nxz.security.handler;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.nxz.security.core.properties.LoginType;
    import com.nxz.security.core.properties.SecurityProperties;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @Component("custAuthenticationSuccessHandler")
    @Slf4j
    public class CustAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    
        @Autowired//springmvc自动注册的一个mapper类
        private ObjectMapper objectMapper;
    
        @Autowired
        private SecurityProperties securityProperties;
    
        @Override
        public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
            log.info("登录成功");
    
            if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())) {
                httpServletResponse.setContentType("application/json;UTF-8");
                httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authentication));
            } else {
                super.onAuthenticationSuccess(httpServletRequest, httpServletResponse, authentication);
            }
    
    
        }
    }
     @Autowired
     private AuthenticationSuccessHandler custAuthenticationSuccessHandler;
    
    
    http.formLogin()
                    .loginPage("/authentication/require").loginProcessingUrl("/authentication/form")
                    .successHandler(custAuthenticationSuccessHandler)
                    .failureHandler(custAuthenticationFailerHandler)
                    .and()
                    .authorizeRequests()
                    .antMatchers("/authentication/require",loginPage,"/code/image").permitAll()
                    .anyRequest()
                    .authenticated();

    三、自定义登录失败的处理

    实现AuthenticationFailureHandler接口,继承SimpleUrlAuthenticationFailureHandler,根据请求方式返回不同的类型

    package com.nxz.security.handler;
    
    import com.alibaba.fastjson.JSON;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.nxz.security.core.properties.LoginType;
    import com.nxz.security.core.properties.SecurityProperties;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @Slf4j
    @Component("custAuthenticationFailerHandler")
    public class CustAuthenticationFailerHandler extends SimpleUrlAuthenticationFailureHandler {
    
        @Autowired
        private ObjectMapper objectMapper;
    
        @Autowired
        private SecurityProperties securityProperties;
    
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
            log.info("登录失败!");
    
            if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())) {
                response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
                response.setContentType("application/json;charset=UTF-8");
                response.getWriter().write(JSON.toJSONString(objectMapper.writeValueAsString(exception)));
            } else {
                super.onAuthenticationFailure(request, response, exception);
            }
    
        }
    }
    @Autowired
        private AuthenticationFailureHandler custAuthenticationFailerHandler;

    四、源码学习

    1、认证流程说明

    登录请求进来是,会先到UsernamePasswordAuthentication类的,其实最先走的是它的父类AbstractAuthenticationProcessingFilter.java,在父类中会走attemptAuthentication方法,父类没有实现,因此会走子类的方法,当认证成功后,会走到最后successFulAuthentication方法

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
                throws IOException, ServletException {
    
         。。。。
         。。。。 Authentication authResult;
    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); }     。。。。     。。。。 // Authentication success if (continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } successfulAuthentication(request, response, chain, authResult); }

    attemptAuthentication方法(子类usernamepasswordAuthenticationFilter.java)

        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);
        。。。。
        。。。。
    username = username.trim();     //这个UsernamepasswordAuthenticationToken其实就是封装了username 和password信息 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); // 这个setDetails会把请求的一些信息设置到authRequest对象中 setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); }

    上边那个this.getAuthenticationManager() 会获取一个authenticationManager类

    作用:收集相关的provider,当请求到的时候,循环判断是否支持当前的provider类型

    public interface AuthenticationManager {
      //authenticate认证方法,交给实现类实现 Authentication authenticate(Authentication var1)
    throws AuthenticationException; }

    他的实现类(用的就是ProviderManger类),不同的provider支持的Authentication是不同的

    例如:UsernamePasswordAuthenticationToken类型的Authentication时,provider就是DaoAuthenticationProvider,

       public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            Class<? extends Authentication> toTest = authentication.getClass();
            AuthenticationException lastException = null;
            AuthenticationException parentException = null;
            Authentication result = null;
            Authentication parentResult = null;
            boolean debug = logger.isDebugEnabled();
            Iterator var8 = this.getProviders().iterator();
    
            while(var8.hasNext()) {
                AuthenticationProvider provider = (AuthenticationProvider)var8.next();
                if (provider.supports(toTest)) {
                    if (debug) {
                        logger.debug("Authentication attempt using " + provider.getClass().getName());
                    }
    
                    try {
                        result = provider.authenticate(authentication);
                        if (result != null) {
                            this.copyDetails(authentication, result);
                            break;
                        }
                    } catch (AccountStatusException var13) {
                        this.prepareException(var13, authentication);
                        throw var13;
                    } catch (InternalAuthenticationServiceException var14) {
                        this.prepareException(var14, authentication);
                        throw var14;
                    } catch (AuthenticationException var15) {
                        lastException = var15;
                    }
                }
            }
    
          。。。。。
        }

    上边那个标红的provider.authenticate方法会走到DaoAuthenticationProvider对象的父类对象的authticate方法,

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
                return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
            });
            String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
            boolean cacheWasUsed = true;
            UserDetails user = this.userCache.getUserFromCache(username);
            if (user == null) {
                cacheWasUsed = false;
    
                try {
             //这块会进入子类DaoAuthenticationPrivoder user
    = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); } catch (UsernameNotFoundException var6) { this.logger.debug("User '" + username + "' not found"); if (this.hideUserNotFoundExceptions) { throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } throw var6; } Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract"); }        try {
           //校验是否锁定。。(userdetails里边的几个返回值)
    this.preAuthenticationChecks.check(user);
           //校验密码是否匹配
    this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); } catch (AuthenticationException var7) { if (!cacheWasUsed) { throw var7; } cacheWasUsed = false; user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication)
           this.preAuthenticationChecks.check(user); this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); }      //后置查询  this.postAuthenticationChecks.check(user); if (!cacheWasUsed) { this.userCache.putUserInCache(user); } Object principalToReturn = user; if (this.forcePrincipalAsString) { principalToReturn = user.getUsername(); } return this.createSuccessAuthentication(principalToReturn, authentication, user); }

    子类DapAuthenticationPrivoder类的

        protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
            this.prepareTimingAttackProtection();
    
            try {
           //这一块就是调用自定义得人UserDetailsService类 UserDetails loadedUser
    = this.getUserDetailsService().loadUserByUsername(username); if (loadedUser == null) { throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation"); } else { return loadedUser; } } catch (UsernameNotFoundException var4) { this.mitigateAgainstTimingAttack(authentication); throw var4; } catch (InternalAuthenticationServiceException var5) { throw var5; } catch (Exception var6) { throw new InternalAuthenticationServiceException(var6.getMessage(), var6); } }

    进入自定义的CusUserDetailsService

    public class MyUserDetailsService implements UserDetailsService {
        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
            //根据username查找用户信息,在这,先手动写一个user信息
            log.info("查找用户信息{}", s);
    
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            String password = encoder.encode("password");
    
            //user前两个参数是进行认证的,第三个参数是当前用户所拥有的权限,security回根据授权代码进行验证
            return new User(s, password, true, true, true, true,
                    AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    //        AuthorityUtils.commaSeparatedStringToAuthorityList 这个方法是将一个字符一“,” 分割开
        }
    }

  • 相关阅读:
    bzoj 1030 [JSOI2007]文本生成器
    Swift 学习笔记 (闭包)
    Swift 学习笔记 (函数)
    HTML 学习笔记 JQueryUI(Interactions,Widgets)
    HTML 学习笔记 JQuery(表单,表格 操作)
    HTML 学习笔记 JQuery(animation)
    HTML 学习笔记 JQuery(盒子操作)
    HTML 学习笔记 JQuery(事件)
    HTML 学习笔记 JQuery(DOM 操作3)
    HTML 学习笔记 JQuery(DOM 操作2)
  • 原文地址:https://www.cnblogs.com/nxzblogs/p/10753342.html
Copyright © 2011-2022 走看看