zoukankan      html  css  js  c++  java
  • 快速搭建基于Spring Boot + Spring Security 环境

    个人博客网:https://wushaopei.github.io/    (你想要这里多有)

    1、Spring Security 权限管理框架介绍

    简介: Spring Security 提供了基于javaEE的企业应有个你软件全面的安全服务。这里特别强调支持使用SPring框架构件的项目,Spring框架是企业软件开发javaEE方案的领导者。

    Spring Security 的两个目标: “认证” 与“授权”。

    • 认证”,是建立一个他声明的主题的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统)。
    • 授权” 指确定一个主体是否允许在你的应用程序执行一个动作的过程。为了抵达需要授权的店,主体的身份已经有认证过程建立。这个概念是通用的而不只在Spring Security中。

               

    在SpringSecurity 中,请求进入后会被拦截器拦截到,并交由认证管理器首先处理;这是在身份验证层,Spring Security 的支持多种认证模式。包括 Basic、Digest、X.509、LDAP、Form(基于表单的认证) 等多种HTTP 认证。

    浅析:

    Basic 认证:在HTTP1.0提出的认证方法,针对特定的用户和资源,需要提供特定的密码认证后方可访问,其中密码是使用明文传输的;一个请求到来时,浏览器弹出对话框,让用户输入用户名和密码,并用BASE 64 进行编码进行传输,服务器接受并解析这些信息,并认证通过,才会允许继续访问。

    Digest认证: 解决Basic 认证的安全问题,当访问时,浏览器依旧弹出对话框,让用户输入用户名和密码,浏览器会对用户名、密码、HTTP请求方法、被请求资源的URI进行组合后进行MD5运算,然后把计算得到的摘要信息发送给服务器;服务器获取报文头部相关认证信息后获取到 username ,同时获取到密码,同样对用户名、密码、HTTP请求方法、被请求资源的URI进行组合后和 response 进行比较,如果相同,才算认证通过。

    2、Spring Security 常用权限拦截器:

            

    主要功能性拦截器有 11 个, FilterChainProxy 对拦截器进行过滤操作并代理执行!

    3、Spring Security 项目 Demo

    (1)环境搭建及使用

    1. 基于Spring Boot + Spring Security 环境
    2. 常用 Case 实现

    (2)创建SpringBoot 工程

                     

    (3)整合SpringSecurity 依赖

    	        <!--springsecurity 主要依赖-->
                    <dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-security</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.security</groupId>
    			<artifactId>spring-security-test</artifactId>
    		</dependency>

    (4)主启动类配置:

    @RestController          //返回响应体为json
    @SpringBootApplication
    @EnableAutoConfiguration  //扫描Bean注入到容器中
    public class Bootstrap {
    
    	public static void main(String[] args) {
    		SpringApplication.run(Bootstrap.class, args);
    	}
            
            @RequestMapping("/") // 测试接口
    	public String home (){
    		return "hello spring boot";
    	}
    
            @RequestMapping("/hello") //测试接口
    	public String hello (){
    		return "hello spring boot";
    	}
    }

    (5)配置 Servlet 初始化

    /**
     * @ClassName ServletInitializer 告诉程序,项目启动时从 Bootstrap 开始
     * @Description TODO
     * @Author wushaopei
     * @Date 2019/9/19 10:30
     * @Version 1.0
     */
    public class ServletInitializer extends SpringBootServletInitializer {
    
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder application){
            return application.sources(Bootstrap.class);
        }
    }
    

    (6)启动SpringBoot 项目- - run 主启动类:

    启动SpringBoot 项目进行接口访问时,弹出登录界面,说明SpringSecurity生效了

               

    4、SpringSecurity拦截配置:

    注意:在 3  的基础上,对工程进一步配置:

    (1)创建 SpringSecurityConfig 类管理 拦截配置:

    @Configuration     // 将Bean 放到容器中管理
    @EnableWebSecurity // 用于将Security 生成 Bean 
    public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
        /*
         * 接口请求拦截
         **/
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()  //安全请求策略
                    .antMatchers("/").permitAll() //可放行请求配置
                    .anyRequest().authenticated()    //其他请求进行拦截
                    .and()
                    .logout().permitAll()  // 注销任意访问
                    .and()
                    .formLogin();
            http.cors().disable();
        }
        /*
        *  前端拦截
        * */
        @Override
        public void configure(WebSecurity web){
            // 忽视 js 、css 、 images 后缀访问
            web.ignoring().antMatchers("/js/**","/css/**","/images/**");
        }
    }
    

    该 SpringSecurityConfig 继承 WebSecurityConfigurerAdapter 类,并重写 configure(HttpSecurity http)和 configure(WebSecurity web)两个方法,分别针对 接口请求 静态页面 资源进行拦截。

    接口请求: ip:port/  的请求一律通过,主要进入到 Bootstrap 中的 RequestMapping(“/”)接口中;并对 “/”后带有标识地址的请求进行拦截认证;

    静态资源: 此处对静态 js 、css、images 三者进行放行。

    效果截图:

    配置 SpringSecurity 后,默认可以通过 “/” 的接口请求; 而当进行 如“/hello”接口请求时会被拦截进行验证

    5、基于SpringSecurity 权限管理 Case 实操

    权限管理 Case 的需求有两个要求:

    1. 第一点 :只要能登录即可;
    2. 第二点:有指定的角色,每个角色有指定的权限.

    (1)只要能登录即可,功能体现:

    在SpringSecurityConfig.java中配置用户信息:

    /*
    *  告诉程序,系统中有个用户 用户名为 admin ,密码为 admin  角色为 ADMIN
    * */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
    
    }

    注意 :

    在springboot使用spring security 做权限管理 ,使用内存用户验证,会返回无响应报错:
    java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

    解决方法:

    这是因为Spring boot 2.0.3引用的security 依赖是 spring security 5.X版本,此版本需要提供一个PasswordEncorder的实例,否则后台汇报错误:
    
    java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
    并且页面毫无响应。
    因此,需要创建PasswordEncorder的实现类。

    创建PasswordEncorder 实体类后可通过两种方式完成明文验证通过:

    第一种:直接在配置好的 PasswordEncorder 类上添加 @Component 装配 MyPasswordEncoder 为Bean 注入到容器即可;

    @Component
    public class MyPasswordEncoder implements PasswordEncoder {
        @Override
        public String encode(CharSequence charSequence) {
            return charSequence.toString();
        }
    
        @Override
        public boolean matches(CharSequence charSequence, String s) {
            return s.equals(charSequence.toString());
        }
    }

    然后继续使用

      /*
        *  告诉程序,系统中有个用户 用户名为 admin ,密码为 admin  角色为 ADMIN
        * */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
            //可以设置内存指定的登录的账号密码,指定角色
            //不加.passwordEncoder(new MyPasswordEncoder())或者注入该类的Bean
            //就不是以明文的方式进行匹配,会报错
            auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
        }

    进行 密码明文登录,可以成功

    第二种:在auth.inMemoryAuthentication()后面使用.passwordEncoder(new MyPasswordEncoder())对登录信息进行包装后提交即可;这样也可以成功

    //这样,页面提交时候,密码以明文的方式进行匹配。
    auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder()).withUser("wsp").password("wsp").roles("ADMIN");
    

    推荐: 最好是使用第一种 装载成 Bean 后注入容器,对于开发效率更高,也更便捷。

    (2)有指定的角色,每个角色有指定的权限,功能体现:

    ① 创建接口 role1() 并对其添加 @PreAuthorize("hasRole('ROLE_ADMIN')")  以进行角色拦截

        @PreAuthorize("hasRole('ROLE_ADMIN')") // 角色拦截校验注解
        @RequestMapping("/roleAuth")
        public String role1(){
    
           return "roleAuth";
        }

    (2)角色拦截 - - 功能解析:

     @PreAuthorize("hasRole('ROLE_ADMIN')") 注解说明:

    ②并在 SpringSecurityConfig.java 中创建新用户,以提供测试:

    @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //可以设置内存指定的登录的账号密码,指定角色
            //不加.passwordEncoder(new MyPasswordEncoder())
            //就不是以明文的方式进行匹配,会报错
            auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
            auth.inMemoryAuthentication().withUser("demo").password("demo").roles("DEMO");
            .passwordEncoder(new MyPasswordEncoder())。
            //这样,页面提交时候,密码以明文的方式进行匹配。
            auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder()).withUser("wsp").password("wsp").roles("ADMIN");
        }

    ③ 创建好接口后,进行登录尝试,发现,使用 用户为 demo ,角色 为 DEMO 依旧可以通过该接口

    问题解析:

    这是因为还需要再添加一个@EnableGlobalMethodSecurity(prePostEnabled = true) 注解到 启动器上,以让 @PreAuthorize("hasRole('ROLE_ADMIN')") 这个注解生效,完整 启动类代码如下:

    @RestController
    @SpringBootApplication
    @EnableAutoConfiguration
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class Bootstrap {
    
    	public static void main(String[] args) {
    		SpringApplication.run(Bootstrap.class, args);
    	}
    	@RequestMapping("/")
    	public String home (){
    		return "hello spring boot";
    	}
    	@RequestMapping("/hello")
    	public String hello (){
    		return "hello spring boot";
    	}
    	@PreAuthorize("hasRole('ROLE_ADMIN')")
    	@RequestMapping("/roleAuth")
    	public String role1(){
    		return "roleAuth";
    	}
    }

    添加注解后

    使用 demo 进行登录:

             

    使用admin进行登录:

             

    (3)数据库管理实现

    通过数据库获取用户信息进行登录认证

    创建 MyUserService.java类,并装载 Bean 到 容器中,在SpringSecurityConfig.java 中进行配置:

    @Component
    public class MyUserService implements UserDetailsService{
        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
            return null;
        }
    }

    SpringSecurityConfig.java 中 configure(AuthenticationManagerBuilder auth)方法修改为通过注入MyUserService 的Bean实现数据库查询

        @Autowired
        private MyUserService myUserService;
    
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
               auth.userDetailsService(myUserService); // 通过注入 MyUserService 的方式实现数据库查询用户信息的登录认证
    
        }

    密码的自定义验证:

    创建 MyPasswordEncoder 类,实现 PasswordEncoder 接口类;重写 encode(CharSequence obj和 matches(CharSequence obj,String str) 方法:

    @Component
    public class MyPasswordEncoder implements PasswordEncoder {
    
        private final static String SALT = "wenmin";
    
        @Override
        public String encode(CharSequence charSequence) {
    
            return MD5Util.MD5Encode(charSequence.toString(),SALT);
        }
    
        @Override
        public boolean matches(CharSequence rawPassword, String encodedPassword) {
    
            return MD5Util.isPasswordValid(encodedPassword,rawPassword.toString(),SALT);
        }
    }

    说明:第一个是加密的方法,第二个是匹配密码,具体操作是加密方法加密后与原始密码在匹配方法中进行匹配

    底层逻辑解析:

    passwordEncoder. isPasswordValid(userDetails.getPassword(), presentedPassword, salt) 这个实现是在接口PasswordEncoder的实现类MessageDigestPasswordEncoder中实现的。 
    
    MessageDigestPasswordEncoder类: 
    public boolean isPasswordValid(String encPass, String rawPass, Object salt) { 
            String pass1 = "" + encPass; 
            String pass2 = encodePassword(rawPass, salt); 
    
            return pass1.equals(pass2); 
        } 
    
    其中 encodePassword(rawPass, salt)方法如下: 
    public String encodePassword(String rawPass, Object salt) { 
            String saltedPass = mergePasswordAndSalt(rawPass, salt, false); 
    
            MessageDigest messageDigest = getMessageDigest(); 
    
            byte[] digest; 
    
            try { 
                digest = messageDigest.digest(saltedPass.getBytes("UTF-8")); 
            } catch (UnsupportedEncodingException e) { 
                throw new IllegalStateException("UTF-8 not supported!"); 
            } 
    
            // "stretch" the encoded value if configured to do so 
            for (int i = 1; i < iterations; i++) { 
                digest = messageDigest.digest(digest); 
            } 
    
            if (getEncodeHashAsBase64()) { 
                return new String(Base64.encode(digest)); 
            } else { 
                return new String(Hex.encode(digest)); 
            } 
        } 
    使用的是MD5加密方法。 

    引入自定义的登录信息验证器

    auth.userDetailsService(myUserService).passwordEncoder(new MyPasswordEncoder());

    相关注解说明:

    @PreAuthorize("hasRole('ROLE_ADMIN')")  方法调用前进行权限检查
    @PostAuthorize("hasRole('')")           方法调用后进行权限检查
    @PreFilter("")                          方法调用前针对集合类参数或返回值进行过滤
    @PostFilter("") 方法调用后针对集合类参数或返回值进行过滤

    6、Spring Security 的优缺点

    优点:

    提供了一套安全框架,而且这个框架是可以用的;

    提供了很多用户认证的功能,实现相关接口即可,节约大量开发工作;

    基于spring,易于集成到 spring 项目中,且封装了许多方法。

    缺点:

    配置文件多,角色被“编码”到配置文件盒源文件中,RBAC 不明显;

    对于系统中用户、角色、权限之间的关系,没有可操作的界面;

    大数据量情况下,几乎不可用。

    https://github.com/wushaopei/SPRING_BOOT/tree/master/Spring-boot-Security

  • 相关阅读:
    HDU 6182 A Math Problem 水题
    HDU 6186 CS Course 位运算 思维
    HDU 6188 Duizi and Shunzi 贪心 思维
    HDU 2824 The Euler function 欧拉函数
    HDU 3037 Saving Beans 多重集合的结合 lucas定理
    HDU 3923 Invoker Polya定理
    FZU 2282 Wand 组合数学 错排公式
    HDU 1452 Happy 2004 数论
    HDU 5778 abs 数论
    欧拉回路【判断连通+度数为偶】
  • 原文地址:https://www.cnblogs.com/wushaopei/p/11681389.html
Copyright © 2011-2022 走看看