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接口

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

实现AuthenticationFailureHandler接口

四、源码学习

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 这个方法是将一个字符一“,” 分割开
    }
}

原文地址:https://www.cnblogs.com/nxzblogs/p/10753342.html

时间: 2024-10-11 13:28:17

spring security 学习二的相关文章

[转]Spring Security学习总结二

原文链接: http://www.blogjava.net/redhatlinux/archive/2008/08/20/223148.html http://www.blogjava.net/redhatlinux/archive/2008/09/01/226010.html [总结-含源码]Spring Security学习总结二 Posted on 2008-09-01 10:08 tangtb 阅读(9518) 评论(12)  编辑  收藏 所属分类: Spring .Spring Se

[转]Spring Security学习总结一

[总结-含源码]Spring Security学习总结一(补命名空间配置) Posted on 2008-08-20 10:25 tangtb 阅读(43111) 评论(27)  编辑  收藏 所属分类: Spring .Spring Security Spring Security学习总结一 在认识Spring Security之前,所有的权限验证逻辑都混杂在业务逻辑中,用户的每个操作以前可能都需要对用户是否有进行该项 操作的权限进行判断,来达到认证授权的目的.类似这样的权限验证逻辑代码被分散

Spring Security 解析(二) —— 认证过程

Spring Security 解析(二) -- 认证过程 ??在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因此决定先把Spring Security .Spring Security Oauth2 等权限.认证相关的内容.原理及设计学习并整理一遍.本系列文章就是在学习的过程中加强印象和理解所撰写的,如有侵权请告知. 项目环境: JDK1.8 Spring boot 2.x Spring Security 5.x 一.@EnableGlobalAuth

Spring Ioc学习(二)

wxWidgets和Codeblocks的编译安装,GUI程序开发平台的搭建具体步骤如下: (1)基本编译环境安装 安装编译工具(gcc之类)sudo apt-get install build-essential 安装X11sudo apt-get install libx11-dev 安装GTK需要的东西sudo apt-get install?gnome-core-devel (2)下载wxWidgets源码包并解压缩到 #{wxdir} (3)创建基于gtk和x11的编译目录${wx}

Spring Security学习总结

1.Spring Security介绍  一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分. 用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统.用户认证一般要求用户提供用户名和密码.系统通过校验用户名和密码来完成认证过程. 用户授权指的是验证某个用户是否有权限执行某个操作.在一个系统中,不同用户所具有的权限是不同的.比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改.一般来说,系统

【Spring Security】二、数据库管理用户权限

一 引入相关的jar包 这个例子用的是mysql数据库和c3p0开源的jdbc连接池,在项目的pom.xml中引入jar包 <!-- Mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.21</version> </dependency>

Spring Security教程(二):自定义数据库查询

Spring Security自带的默认数据库存储用户和权限的数据,但是Spring Security默认提供的表结构太过简单了,其实就算默认提供的表结构很复杂,也不一定能满足项目对用户信息和权限信息管理的要求.那么接下来就讲解如何自定义数据库实现对用户信息和权限信息的管理. 一.自定义表结构 这里还是用的mysql数据库,所以pom.xml文件都不用修改.这里只要新建三张表即可,user表.role表.user_role表.其中user用户表,role角色表为保存用户权限数据的主表,user_

Spring Security 学习总结(2) 持续更新中

从一个最简单的Spring Security Java Configuration 看起 @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter {     @Autowired     public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {    

spring security 学习文档

web service Prepared by:   Sea                                                                                                                                                            29 April, 2018 Contents 1.             The description of spring