美文网首页SpringSecurity
一、SpringSecurity简介

一、SpringSecurity简介

作者: 紫荆秋雪_文 | 来源:发表于2020-05-03 20:02 被阅读0次

    源码下载

    一、SpringSecurity核心功能

    认证 (你是谁)

    授权 (你能干什么)

    攻击防护 (防止伪造身份)

    二、环境搭建

    security-core       :负责SpringSecurity主要通用的逻辑
    security-browser    :负责SpringSecurity处理通用的浏览器相关逻辑
    security-app        :负责SpringSecurity处理通用的APP相关逻辑
    security-demo       :负责测试 security-browser 和 security-app
    

    项目结构

    项目结构.png 项目结构.png

    三、父类pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.raven</groupId>
        <artifactId>security</artifactId>
        <packaging>pom</packaging>
        <version>1.0-SNAPSHOT</version>
        <modules>
            <module>security-core</module>
            <module>security-demo</module>
            <module>security-app</module>
            <module>security-browser</module>
        </modules>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>io.spring.platform</groupId>
                    <artifactId>platform-bom</artifactId>
                    <version>Cairo-SR7</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-parent</artifactId>
                    <version>2.0.8.RELEASE</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <resources>
                <resource>
                    <directory>src/main/java</directory>
                    <filtering>false</filtering>
                    <includes>
                        <include>**/*.xml</include>
                    </includes>
                </resource>
                <resource>
                    <directory>src/main/resources</directory>
                    <filtering>false</filtering>
                </resource>
            </resources>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <fork>true</fork>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    

    四、测试

    1、在通常没有如何安全框架接入的项目时,发起请求如下 没有接入security.png

    2、接入Security时

    • 显示界面,用户名默认为user 接入security.png
    • 控制台输出默认密码

    Using generated security password: edf735f4-773b-4de7-bc91-cc3ad0fb8b67
    
    • security-core模块pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>security</artifactId>
            <groupId>com.raven</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>security-core</artifactId>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.social</groupId>
                <artifactId>spring-social-config</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.social</groupId>
                <artifactId>spring-social-core</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.social</groupId>
                <artifactId>spring-social-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.social</groupId>
                <artifactId>spring-social-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>8</source>
                        <target>8</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    
    </project>
    
    
    • security-browser模块pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>security</artifactId>
            <groupId>com.raven</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>security-browser</artifactId>
    
        <dependencies>
            <dependency>
                <groupId>com.raven</groupId>
                <artifactId>security-core</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    
    </project>
    
    
    • security-demo模块pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>security</artifactId>
            <groupId>com.raven</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>security-demo</artifactId>
    
        <dependencies>
            <dependency>
                <groupId>com.raven</groupId>
                <artifactId>security-browser</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    
    </project>
    
    

    五、注意

    在第一次此时时,有可能控制台不会输出默认密码也不会弹出默认的登录界面,请在Demo的启动类中添加扫描注解,可以扫描到security-core包

    @SpringBootApplication
    @ComponentScan("com.raven")
    public class DemoApplication {
        public static void main(String[] args) {
            try {
                SpringApplication.run(DemoApplication.class, args);
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    六、SpringSecurity原理 SpringSecurity原理.png

    • UsernamePasswordAuthenticationFilter : 用来拦截formLogin表单提交请求
    • BasicAuthenticationFilter : 用来拦截httpBasic请求
    • ExceptionTranslationFilter : 用来处理异常处理
    • FilterSecurityInterceptor :

    当使用formLogin表单登录时,会进入UsernamePasswordAuthenticationFilter过滤器

    • 1、UsernamePasswordAuthenticationFilter源码
    public class UsernamePasswordAuthenticationFilter extends
            AbstractAuthenticationProcessingFilter {
    
        public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
        public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
    
        private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
        private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
        private boolean postOnly = true;
     
        //构造器
        public UsernamePasswordAuthenticationFilter() {
            super(new AntPathRequestMatcher("/login", "POST"));
        }
    
    
        // 构造 Authentication
        public Authentication attemptAuthentication(HttpServletRequest request,
                HttpServletResponse response) throws AuthenticationException {
            // 请求方式必须是POST
            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();
    
            // 通过请求中 用户名 密码来构造 UsernamePasswordAuthenticationToken
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                    username, password);
    
            // Allow subclasses to set the "details" property
            setDetails(request, authRequest);
    
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    
        
        protected String obtainPassword(HttpServletRequest request) {
            return request.getParameter(passwordParameter);
        }
    
        
        protected String obtainUsername(HttpServletRequest request) {
            return request.getParameter(usernameParameter);
        }
    
        
        protected void setDetails(HttpServletRequest request,
                UsernamePasswordAuthenticationToken authRequest) {
            authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
        }
    
        
        public void setUsernameParameter(String usernameParameter) {
            Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
            this.usernameParameter = usernameParameter;
        }
    
        
        public void setPasswordParameter(String passwordParameter) {
            Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
            this.passwordParameter = passwordParameter;
        }
    
        
        public void setPostOnly(boolean postOnly) {
            this.postOnly = postOnly;
        }
    
        public final String getUsernameParameter() {
            return usernameParameter;
        }
    
        public final String getPasswordParameter() {
            return passwordParameter;
        }
    }
    
    
    • 2、通过请求参数中的用户名、密码来构建UsernamePasswordAuthenticationToken 最后通过AuthenticationManager通过遍历AuthenticationProvider选中处理UsernamePasswordAuthenticationToken的DaoAuthenticationProvider来验证码用户名和密码,校验成功后就可以访问REST API

    • 3、ProviderManager源码

    public class ProviderManager implements AuthenticationManager, MessageSourceAware,
            InitializingBean {
        // ~ Static fields/initializers
        // =====================================================================================
    
        private static final Log logger = LogFactory.getLog(ProviderManager.class);
    
        // ~ Instance fields
        // ================================================================================================
    
        private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
        private List<AuthenticationProvider> providers = Collections.emptyList();
        protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
        private AuthenticationManager parent;
        private boolean eraseCredentialsAfterAuthentication = true;
    
        public ProviderManager(List<AuthenticationProvider> providers) {
            this(providers, null);
        }
    
        public ProviderManager(List<AuthenticationProvider> providers,
                AuthenticationManager parent) {
            Assert.notNull(providers, "providers list cannot be null");
            this.providers = providers;
            this.parent = parent;
            checkState();
        }
    
        // ~ Methods
        // ========================================================================================================
    
        public void afterPropertiesSet() throws Exception {
            checkState();
        }
    
        private void checkState() {
            if (parent == null && providers.isEmpty()) {
                throw new IllegalArgumentException(
                        "A parent AuthenticationManager or a list "
                                + "of AuthenticationProviders is required");
            }
        }
    
        /**
         * Attempts to authenticate the passed {@link Authentication} object.
         * <p>
         * The list of {@link AuthenticationProvider}s will be successively tried until an
         * <code>AuthenticationProvider</code> indicates it is capable of authenticating the
         * type of <code>Authentication</code> object passed. Authentication will then be
         * attempted with that <code>AuthenticationProvider</code>.
         * <p>
         * If more than one <code>AuthenticationProvider</code> supports the passed
         * <code>Authentication</code> object, the first one able to successfully
         * authenticate the <code>Authentication</code> object determines the
         * <code>result</code>, overriding any possible <code>AuthenticationException</code>
         * thrown by earlier supporting <code>AuthenticationProvider</code>s.
         * On successful authentication, no subsequent <code>AuthenticationProvider</code>s
         * will be tried.
         * If authentication was not successful by any supporting
         * <code>AuthenticationProvider</code> the last thrown
         * <code>AuthenticationException</code> will be rethrown.
         *
         * @param authentication the authentication request object.
         *
         * @return a fully authenticated object including credentials.
         *
         * @throws AuthenticationException if authentication fails.
         */
        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();
    
            for (AuthenticationProvider provider : getProviders()) {
                if (!provider.supports(toTest)) {
                    continue;
                }
    
                if (debug) {
                    logger.debug("Authentication attempt using "
                            + provider.getClass().getName());
                }
    
                try {
                    result = provider.authenticate(authentication);
    
                    if (result != null) {
                        copyDetails(authentication, result);
                        break;
                    }
                }
                catch (AccountStatusException e) {
                    prepareException(e, authentication);
                    // SEC-546: Avoid polling additional providers if auth failure is due to
                    // invalid account status
                    throw e;
                }
                catch (InternalAuthenticationServiceException e) {
                    prepareException(e, authentication);
                    throw e;
                }
                catch (AuthenticationException e) {
                    lastException = e;
                }
            }
    
            if (result == null && parent != null) {
                // Allow the parent to try.
                try {
                    result = parentResult = parent.authenticate(authentication);
                }
                catch (ProviderNotFoundException e) {
                    // ignore as we will throw below if no other exception occurred prior to
                    // calling parent and the parent
                    // may throw ProviderNotFound even though a provider in the child already
                    // handled the request
                }
                catch (AuthenticationException e) {
                    lastException = parentException = e;
                }
            }
    
            if (result != null) {
                if (eraseCredentialsAfterAuthentication
                        && (result instanceof CredentialsContainer)) {
                    // Authentication is complete. Remove credentials and other secret data
                    // from authentication
                    ((CredentialsContainer) result).eraseCredentials();
                }
    
                // If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
                // This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
                if (parentResult == null) {
                    eventPublisher.publishAuthenticationSuccess(result);
                }
                return result;
            }
    
            // Parent was null, or didn't authenticate (or throw an exception).
    
            if (lastException == null) {
                lastException = new ProviderNotFoundException(messages.getMessage(
                        "ProviderManager.providerNotFound",
                        new Object[] { toTest.getName() },
                        "No AuthenticationProvider found for {0}"));
            }
    
            // If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
            // This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
            if (parentException == null) {
                prepareException(lastException, authentication);
            }
    
            throw lastException;
        }
    
        @SuppressWarnings("deprecation")
        private void prepareException(AuthenticationException ex, Authentication auth) {
            eventPublisher.publishAuthenticationFailure(ex, auth);
        }
    
        /**
         * Copies the authentication details from a source Authentication object to a
         * destination one, provided the latter does not already have one set.
         *
         * @param source source authentication
         * @param dest the destination authentication object
         */
        private void copyDetails(Authentication source, Authentication dest) {
            if ((dest instanceof AbstractAuthenticationToken) && (dest.getDetails() == null)) {
                AbstractAuthenticationToken token = (AbstractAuthenticationToken) dest;
    
                token.setDetails(source.getDetails());
            }
        }
    
        public List<AuthenticationProvider> getProviders() {
            return providers;
        }
    
        public void setMessageSource(MessageSource messageSource) {
            this.messages = new MessageSourceAccessor(messageSource);
        }
    
        public void setAuthenticationEventPublisher(
                AuthenticationEventPublisher eventPublisher) {
            Assert.notNull(eventPublisher, "AuthenticationEventPublisher cannot be null");
            this.eventPublisher = eventPublisher;
        }
    
        /**
         * If set to, a resulting {@code Authentication} which implements the
         * {@code CredentialsContainer} interface will have its
         * {@link CredentialsContainer#eraseCredentials() eraseCredentials} method called
         * before it is returned from the {@code authenticate()} method.
         *
         * @param eraseSecretData set to {@literal false} to retain the credentials data in
         * memory. Defaults to {@literal true}.
         */
        public void setEraseCredentialsAfterAuthentication(boolean eraseSecretData) {
            this.eraseCredentialsAfterAuthentication = eraseSecretData;
        }
    
        public boolean isEraseCredentialsAfterAuthentication() {
            return eraseCredentialsAfterAuthentication;
        }
    
        private static final class NullEventPublisher implements AuthenticationEventPublisher {
            public void publishAuthenticationFailure(AuthenticationException exception,
                    Authentication authentication) {
            }
    
            public void publishAuthenticationSuccess(Authentication authentication) {
            }
        }
    }
    
    
    • 4、DaoAuthenticationProvider源码
    public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
        // ~ Static fields/initializers
        // =====================================================================================
    
        /**
         * The plaintext password used to perform
         * PasswordEncoder#matches(CharSequence, String)}  on when the user is
         * not found to avoid SEC-2056.
         */
        private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
    
        // ~ Instance fields
        // ================================================================================================
    
        private PasswordEncoder passwordEncoder;
    
        /**
         * The password used to perform
         * {@link PasswordEncoder#matches(CharSequence, String)} on when the user is
         * not found to avoid SEC-2056. This is necessary, because some
         * {@link PasswordEncoder} implementations will short circuit if the password is not
         * in a valid format.
         */
        private volatile String userNotFoundEncodedPassword;
    
        private UserDetailsService userDetailsService;
    
        public DaoAuthenticationProvider() {
            setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
        }
    
        // ~ Methods
        // ========================================================================================================
    
        @SuppressWarnings("deprecation")
        protected void additionalAuthenticationChecks(UserDetails userDetails,
                UsernamePasswordAuthenticationToken authentication)
                throws AuthenticationException {
            if (authentication.getCredentials() == null) {
                logger.debug("Authentication failed: no credentials provided");
    
                throw new BadCredentialsException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.badCredentials",
                        "Bad credentials"));
            }
    
            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"));
            }
        }
    
        protected void doAfterPropertiesSet() throws Exception {
            Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
        }
    
        protected final UserDetails retrieveUser(String username,
                UsernamePasswordAuthenticationToken authentication)
                throws AuthenticationException {
            prepareTimingAttackProtection();
            try {
                UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
                if (loadedUser == null) {
                    throw new InternalAuthenticationServiceException(
                            "UserDetailsService returned null, which is an interface contract violation");
                }
                return loadedUser;
            }
            catch (UsernameNotFoundException ex) {
                mitigateAgainstTimingAttack(authentication);
                throw ex;
            }
            catch (InternalAuthenticationServiceException ex) {
                throw ex;
            }
            catch (Exception ex) {
                throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
            }
        }
    
        private void prepareTimingAttackProtection() {
            if (this.userNotFoundEncodedPassword == null) {
                this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
            }
        }
    
        private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
            if (authentication.getCredentials() != null) {
                String presentedPassword = authentication.getCredentials().toString();
                this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
            }
        }
    
        /**
         * Sets the PasswordEncoder instance to be used to encode and validate passwords. If
         * not set, the password will be compared using {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}
         *
         * @param passwordEncoder must be an instance of one of the {@code PasswordEncoder}
         * types.
         */
        public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
            Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
            this.passwordEncoder = passwordEncoder;
            this.userNotFoundEncodedPassword = null;
        }
    
        protected PasswordEncoder getPasswordEncoder() {
            return passwordEncoder;
        }
    
        public void setUserDetailsService(UserDetailsService userDetailsService) {
            this.userDetailsService = userDetailsService;
        }
    
        protected UserDetailsService getUserDetailsService() {
            return userDetailsService;
        }
    }
    
    

    相关文章

      网友评论

        本文标题:一、SpringSecurity简介

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