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

  • 相关阅读:
    浅拷贝与深拷贝
    Hibernate入门(1)-第一个Hibernate程序
    Spring入门(10)-Spring JDBC
    【Java,IDEA】创建自己的代码模版快速生成
    【Java,IDEA】使用IDEA自动生成序列化ID
    【Project】原生JavaWeb工程 02 登陆业务的流程(第一阶段样例)
    【AJAX】Asynchronous JavaScript And XML (非同步的JS & XML)
    【JS】06 语法补充
    【JS】05 DOM 文档对象模型 P2 元素的CRUD、Dom集合对象
    【JS】04 DOM 文档对象模型 P1 查找元素、改变内容、绑定事件
  • 原文地址:https://www.cnblogs.com/wushaopei/p/11681389.html
Copyright © 2011-2022 走看看