zoukankan      html  css  js  c++  java
  • 用SpringSecurity从零搭建pc项目-01

    注:之前写过一些列的SpringSecurity的文章,重新写一遍是为了把某些不必要的步骤省去,留下精简的,因为工作中有一些不需要。

    在java的权限框架里,shiro和SpringSecurity是用的最多的。随着springboot的流行,SpringSecurity也越来越火了,因为springboot默认支持SpringSecurity。所以很有必要把SpringSecurity也搞明白。shiro更加轻量级,SpringSecurity的功能更加丰富。

    软件环境:

      开发工具:Idea

      JDK:1.8   

      maven:3.3.9

         * SpringBoot版本:1.5.18  (说明:springboot版本没有用最新版的2.0x,因为2.0x和1.5 差别比较大吧,2.x好似是基于spring5的,SpringSecurity的更新速度好似跟不上springboot,所以这里如果用springboot 2.x会有问题,保险起见这里就不求新了)

    SpringSecurity核心功能:认证、授权、攻击防护(防止伪造身份)

    话不多说,下边开始打代码。

    一、项目搭建

    1.1  热身 ,SpringSecurity默认的Httpbasic鉴权

    1.1.1,新建项目

    填好项目信息,next

    选启动器:SpringSecurity、Web

    设置项目目录。finish

    建好后的项目结构:security包放有关SpringSecurity的代码

    maven依赖:

    <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>

    引入了SpringSecurity的starter后,会自动引入SpringSecurity相关的相关依赖:

     1.1.2 访问我们的服务

     新建一个Controller:

    @RestController
    public class UserController {
    
        @RequestMapping("/hello")
        public String hello(){
            return "Hello ! ";
        }
    }

    启动项目,访问localhost:8080/hello,默认就会出来这个登录框,只要引入了SpringSecurity,springboot就默认会启用http basic认证,所有的服务都会被保护起来, ,。

    其中的默认用户名是user,默认的密码在启动项目的时候会在控制台打印。

    输入用户名密码,就能访问:

    如果输入的用户名或密码错误,Springboot会引导你到一个空白页,这个也是可以配置的,后边再说怎么配置:

    你还可以关闭这个默认的鉴权,只需在application.properties 里配置:  security.basic.enabled = false ,这个值默认是true。

    二、表单登录

    如果想去掉那个比较丑的basic登录框,只需要一个配置类即可。新建配置类 BrowserSecurityConfig,继承  WebSecurityConfigurerAdapter

    @EnableWebSecurity
    public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
        
    }

    (为什么这么写?因为SpringSecurity官方文档这么说的https://docs.spring.io/spring-security/site/docs/4.2.10.RELEASE/reference/htmlsingle/#samples):

        5.1 Hello Web Security Java配置

        第一步是创建Spring Security Java配置。配置创建一个Servlet过滤器,称为springSecurityFilterChain负责应用程序内所有安全性(保护应用程序URL,验证提交的用户名和密码,重定向到登录表单等)。您可以在下面找到Spring Security Java配置的最基本示例:

    import org.springframework.beans.factory.annotation.Autowired;
    
    import org.springframework.context.annotation.*;
    import org.springframework.security.config.annotation.authentication.builders.*;
    import org.springframework.security.config.annotation.web.configuration.*;
    
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Bean
        public UserDetailsService userDetailsService() throws Exception {
            InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
            manager.createUser(User.withUsername("user").password("password").roles("USER").build());
            return manager;
        }
    }
    然后启动application,访问http://localhost:8080/hello,可以看到已经出来了一个登录页面,输入用户名user,以及控制台打印的密码:

    登录后 访问的是登录之前的要访问的hello服务:

    官网说明:

    到目前为止,我们的WebSecurityConfig仅包含有关如何验证用户身份的信息。Spring Security如何知道我们要求所有用户进行身份验证?Spring Security如何知道我们想要支持基于表单的身份验证?原因是WebSecurityConfigurerAdapterconfigure(HttpSecurity http)方法中提供了一个默认配置,如下所示:

    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .and()
            .httpBasic();
    }

    三、自定义用户认证逻辑

    3.1 ,处理用户信息获取 、用户校验  、密码的加密解密

    新建一个类,MyUserDetailService (这个类必须是Spring里的一个Bean,所以加上@Component注解)实现 UserDetailsService 接口,UserDetailsService 接口只有一个方法:通过用户名查询用户信息,返回UserDetail

    UserDetailsService.java 
    public interface UserDetailsService {
        UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
    }
    UserDetails.java : 提供了几个方法,账户是否启用、账户是否过期、密码是否过期、账户是否锁定、权限集合信息
    public interface UserDetails extends Serializable {
        Collection<? extends GrantedAuthority> getAuthorities();
    
        String getPassword();
    
        String getUsername();
    
        boolean isAccountNonExpired();
    
        boolean isAccountNonLocked();
    
        boolean isCredentialsNonExpired();
    
        boolean isEnabled();
    }

     配置一个密码的加密解密器类:PasswordEncoder,我们用其一个实现类 BCryptPasswordEncoder,只有两个方法,一个是加密密码,一个是匹配方法。如果已有系统已经有了自己的加密算法,这里可以换成自己的加解密逻辑。

      @Bean
        public PasswordEncoder passwordencoder(){
            //BCryptPasswordEncoder implements PasswordEncoder
            return new BCryptPasswordEncoder();
        }

    这个接口的实现类会给加密的密码随机加盐,所以一样的密码每次加密出来是不一样的,更安全。如123456加密2次:

    加密后密码:  $2a$10$BChH.C4.X8MYuI1mHFoOkefWhOsad7SvhZedHFt1OG4vjSu.z9weC

    加密后密码:  $2a$10$YUbz.miE5C0aAcuU1FnHSu/U.Qm/BujTNw6X7S5i4/6AhjyDc6suK

     此时我们的逻辑是,只要密码输入123456,就能登录成功。启动项目试验:访问/hello ,会自动跳转/login ,随便输入用户名aaaaaa,输入密码123456 ,成功访问Hello!

        |     

     3.2 自定义登录页面

    用SpringSecurity提供的默认的登录页肯定是不行的,所以需要自定义登录页(当然这里还可以配置成一个Controller方法,然后跳转到登录页。或者从配置文件里读取),在BrowserSecurityConfig类里配置:

    @Override
        protected void configure(HttpSecurity http) throws Exception {
            //http.httpBasic() //这个就是默认的弹框认证
            http.formLogin() //表单认证
                    .loginPage("/login.html")
                    .loginProcessingUrl("/authentication/form")
                    .and()
                    .authorizeRequests() //下边的都是授权的配置
                    .antMatchers("/login.html").permitAll() //放过登录页不过滤
                    .anyRequest()        //任何请求
                    .authenticated();    //都需要身份认证
        }
    .loginPage("/login.html") 配置登录页面
    .loginProcessingUrl("/authentication/form") 配置处理登录表单的action,这个值默认在UsernamePasswordAuthenticationFilter 类里,

       注意一定要放过登录页不过滤,否则一直不能跳转到login.html 

    此时访问 /hello ,跳转到自定义的登录页:

    随便输入用户名,密码输入123456 ,登录,出现

    SpringSecurity默认提供了CSRF(跨站请求伪造)防护,是用CSRF token来完成防护的。暂时关闭CSRF防护,在BrowserSecurityConfig里设置:

    .csrf().disable();
    再次登录,可以成功访问到 /hello服务。
     至此自定义登录页面完成吗,下一步自定义登录成功处理。
    也可以把登录页做成在配置文件application.properties里配置,然后在代码里读取配置,这样更灵活、更通用。
    这里有springboot读取配置文件的用法:https://www.cnblogs.com/lihaoyang/p/10223339.html

     3.2 自定义登录成功/失败处理

    现在的流程是,访问一个需要登录的服务,如果没有登录,跳转登录页,等你登录后,立马就访问登录之前的服务。如果你想在登录成功后做一些处理,比如签到,送积分等等。那就需要自定义登录成功的处理。如果没有这种需求,这一步骤可以略过。

    springsecurity提供了一个接口,AuthenticationSuccessHandler,用来处理登录成功后的逻辑:

    /**
     * Strategy used to handle a successful user authentication.
     * <p>
     * Implementations can do whatever they want but typical behaviour would be to control the
     * navigation to the subsequent destination (using a redirect or a forward). For example,
     * after a user has logged in by submitting a login form, the application needs to decide
     * where they should be redirected to afterwards (see
     * {@link AbstractAuthenticationProcessingFilter} and subclasses). Other logic may also be
     * included if required.
     *
     * @author Luke Taylor
     * @since 3.0
     */
    public interface AuthenticationSuccessHandler {
    
        /**
         * Called when a user has been successfully authenticated.
         *
         * @param request the request which caused the successful authentication
         * @param response the response
         * @param authentication the <tt>Authentication</tt> object which was created during
         * the authentication process.
         */
        void onAuthenticationSuccess(HttpServletRequest request,
                HttpServletResponse response, Authentication authentication)
                throws IOException, ServletException;
    
    }

    1,定义一个自己的成功处理器,实现这个Handler:

    package com.lhy.browser.security.authentication;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * 认证成功处理器
     */
    @Component("myAuthenticationSuccessHandler")
    public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    
        private Logger logger = LoggerFactory.getLogger(getClass());
    
        //springmvc启动会自动注册一个ObjectMapper
        @Autowired
        private ObjectMapper objectMapper;
    
    
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    
            logger.info("登录成功");
            //把authentication返回给响应
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(authentication));
    
        }
    }

    2、添加到配置类,让spring security执行自定义的处理器

     在 BrowserSecurityConfig  配置类里注入 AuthenticationSuccessHandler

    @EnableWebSecurity
    public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
        //自定义的认证成功的处理器
        @Autowired
        private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
        //自定义的认证失败的处理器
        @Autowired
        private AuthenticationFailureHandler myAuthenticationFailureHandler;
    
    
        @Autowired
        private SecurityProperties securityProperties;
    
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //http.httpBasic() //这个就是默认的弹框认证
            http.formLogin() //表单认证
                    .loginPage(securityProperties.getBrowser().getLoginPage())//登录页
                    .loginProcessingUrl("/authentication/form") //登录提交action
                    .successHandler(myAuthenticationSuccessHandler) //自定义的认证成功处理器
                    .failureHandler(myAuthenticationFailureHandler)//自定义的认证失败的处理器
                    .and()
                    .authorizeRequests() //下边的都是授权的配置
                    .antMatchers(securityProperties.getBrowser().getLoginPage()).permitAll() //放过登录页不过滤
                    .anyRequest()        //任何请求
                    .authenticated()    //都需要身份认证
                    .and()
                    .csrf().disable();
        }
    
    
    }

    登录失败处理器和登录成功类似,自定义失败处理类,实现  AuthenticationFailureHandler  接口即可:

    /**
     * 认证失败处理器
     */
    @Component("/myAuthenticationFailureHandler")
    public class MyAuthenticationFailureHandler  implements AuthenticationFailureHandler {
    
        private Logger logger = LoggerFactory.getLogger(getClass());
    
        //springmvc启动会自动注册一个ObjectMapper
        @Autowired
        private ObjectMapper objectMapper;
    
    
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
            logger.info("登录失败");
    
            //把authentication返回给响应
            //状态码500,服务器内部错误
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(exception.getMessage()));
        }
    }

    这样就可以在登录成功和失败后进入自己的处理逻辑了。

  • 相关阅读:
    FTP与HTTP上传文件的对比
    【FTP】Wireshark学习FTP流程
    【CSS】div
    浏览器URL中“#” “?” &“”作用
    【EF】vs2017中没有EF模型
    C# List的使用
    C# Dictionary使用
    Git/GitHub的一些问题
    PHP中的break与continue
    css使文字垂直水平居中
  • 原文地址:https://www.cnblogs.com/lihaoyang/p/10174127.html
Copyright © 2011-2022 走看看