Spring Security

1、框架简介

1.1、概要

        基于spring框架,提供了一套Web应用安全性的完整解决方案。

        一般来说,Web应用的安全性包括用户认证用户授权两个部分,这也是Spring Security的重要核心功能。

        1)用户认证:系统认为用户是否能登录。

        2)用户授权:系统判断用户是否有权限去做某些事情。

1.2、Spring Security与Shiro的比较

        1)Spring Security 特点 :

                与Spring无缝整合。

                全面的权限控制。

                专门为Web开发而设计:旧版本不能脱离Web环境使用;新版本对整个框架进行了分层抽取,分成了核心模块和Web模块。单独引入核心模块就可以脱离Web环境。

                重量级。

        2)Shiro 特点:

                轻量级。

                通用性:

                        好处:不局限于Web环境,可以脱离Web环境使用。

                        缺陷:在Web环境下一些特定的需求需要手动编写代码定制。

        常见的安全管理技术栈的组合:SSM+Shiro;Spring Boot/Spring Cloud+Spring Security。

1.3、模块划分

        1)Core        核心                                                 2)Remoting

        3)Web        web模块                                           4)Config        配置

        5)LDAP                                                               6)OAuth 2.0 Core

        7)OAuth 2.0 Client                                              8)OAuth 2.0 JOSE

        9)OAuth 2.0 Resource Server                            10)ACL

        11)CAS                                                                12)OpenID

        13)Test

2、入门案例

2.1、过程

        第一步:创建springboot工程。

        

        第二步:引入相关依赖。

        

        第三步:编写controller进行测试。

@RestController
@RequestMapping("/test")
public class TestController{
    
    @GetMapping("/hello")
    public String add(){
        return "hello security";
    }

      启动: 访问localhost:8080/hello  。

         

3、基本原理

3.1、过滤器链

        Spring Security 本质是一个过滤器链,大约有十几个过滤器。

        代码底层流程:(三个过滤器为例)

                FilterSecurityInterceptor:是一个方法级的权限过滤器 ,基本位于过滤链的最底部。【InterceptorStatusToken token = super.beforeInvocation(fi)查看之前的过滤器是否放行】

                ExceptionTranslationFilter:是个异常过滤器,用来处理在认证授权过程中抛出的异常。

                UsernamePasswordAuthenticationFilter:对/login的POST请求做拦截,校验表单中用户名,密码。

3.2、加载过滤器

        第一步:使用SpringSecurity配置过滤器 DelegatingFilterProxy。

        第二步:doFilter()方法。

        第三步:初始化 initDelegate()。

        第四步:获取过滤器链 getFilters()。

3.3、两个重要接口

        1)UserDetailsService接口:查询用户名和密码过程

        当什么也没有配置时,账号和密码是有SpringSecurity定义生成的。而在实际开发中账号和密码都是从数据库中查询出来的,所以我们需要通过自定义逻辑控制认证逻辑。

        当自定义逻辑时,需要实现UserDetailsService接口。

        实现过程:

                第一步:创建类继承UsernamePasswordAuthenticationFilter,重写三个方法。

                第二步:创建类实现UserDetailService,编写查询数据过程,返回User对象,这个User对象是安全框架提供对象。

        2)PasswordEncoder接口:数据加密接口,用于返回User对象里面密码加密。

4、Web权限方案

4.1、设置登录的用户名和密码

                第一种方式:通过配置文件。

                        在application.properties中配置。

spring.security.user.name = root

spring.security.user.password = root

                第二种方式:通过配置类。

@Configuration
public class SecurityConfig extend WebSecurityConfigurerAdapter{
    //注入PasswordEncoder类到spring容器中
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswwordEncoder();
    }
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{
        BCryptPasswwordEncoder passwordEncoder = new BCryptPasswwordEncoder();
        String passwword = passwordEncoder.encode("123");
        auth.inMemoryAuthentication().withUser("lucy").password(password).roles("admin");
    }
}

                第三种方式:自定义编写实现类。

                        第一步:创建配置类,设置使用哪个userDetailsService实现类。

@Configuration
public class SecurityConfig extend WebSecurityConfigurerAdapter{
    //注入PasswordEncoder类到spring容器中
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswwordEncoder();
    }

    @Autowired
    private UserDetailsService userDetailsService;
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
}

                第二步:编写实现类,返回User对象,User对象有用户名密码和操作权限。

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException{
        List<GranteAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        return new User("marry", new BCryptPasswordEncoder().encoder("123"),auths);
    }

4.2、实现数据库认证来完成用户登录

                 【整合mybatisplus完成数据库操作】

               第一步:引入相关依赖(MyBatisPlus,mysql,lombok)。

               第二步:创建数据库和数据库表。

               第三步:创建users表对应的实体类。

               第四步:整合mp,创建接口,继承mp接口。

                        【public interface UserMapper extends BaseMapper<User>】

                第五步:在MyUserDetailsService调用mapper里面的方法查询数据库进行用户认证。

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
        //调用UserMapper方法,根据用户名查询数据库
        QueryWrapper<User> wrapper = new QueryWrapper();
        wrapper.eq("username",username);
        User user = userMapper.selectOne(wrapper);
        //判断
        if(user == null){//数据库没有用户名,认证失败
            throw new UsernameNotFoundException("用户名不存在!");
        }

        List<GranteAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        //从查询数据库返回User对象,得到用户名和密码,返回
        return new User(user.getUsername(), new BCryptPasswordEncoder().encoder(user.getPassword()),auths);
    }
}

        第六步:在启动类上面添加注解@MapperScan(mapper所在包路径) 。

        第七步:配置数据库信息(application.properties)。

4.3、自定义设置登录页面   

        第一步:在配置类实相关的配置。

@Configuration
public class SecurityConfig extend WebSecurityConfigurerAdapter{
    //注入PasswordEncoder类到spring容器中
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswwordEncoder();
    }

    @Autowired
    private UserDetailsService userDetailsService;
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.formLogin()  //自定义自己编写的登录页面
            .loginPage("/login.html")  //登录页面设置
            .loginProcessingUrl("/user/login")  //登录访问路径
            .defaultSuccessUrl("/test/index").permitAll()  //登陆成功后,跳转路径
            .and().authorizeRequests()
                .antMatchers("/","/test/hello","/user/login").permitAll()  //设置哪些路径可以直接访问,不需要认证
            .anyRequest().authenticated()
            .and().csrf().disable();  //关闭csrf防护
    }
}

        第二步:创建相关页面,controller 。

【注意:前端表单的name属性必须叫username和password】

 

4.4、基于角色或权限进行访问控制

        1)hasAuthority方法 (单个权限)

        如果当前的主体具有指定的权限,则返回true,否则返回false。

        第一步:在配置类设置当前访问地址有哪些权限。

@Configuration
public class SecurityConfig extend WebSecurityConfigurerAdapter{
    //注入PasswordEncoder类到spring容器中
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswwordEncoder();
    }

    @Autowired
    private UserDetailsService userDetailsService;
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.formLogin()  //自定义自己编写的登录页面
            .loginPage("/login.html")  //登录页面设置
            .loginProcessingUrl("/user/login")  //登录访问路径
            .defaultSuccessUrl("/test/index").permitAll()  //登陆成功后,跳转路径
            .and().authorizeRequests()
                .antMatchers("/","/test/hello","/user/login").permitAll()  //设置哪些路径可以直接访问,不需要认证
            //当前登录用户,只有具有admins权限才可以访问这个路径
            .antMatchers("/test/index").hasAuthority("role")
            .anyRequest().authenticated()
            .and().csrf().disable();  //关闭csrf防护
    }
}

        第二步:在UserDetailsService,把返回User对象设置权限(上述UserDetalsService中,如下图就是):

   

        没有访问权限  403(type = Forbidden,status = 403)。

        2)hasAnyAuthority方法(多个权限)

        如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true。

        第一步:在配置类设置当前访问地址有哪些权限。

@Configuration
public class SecurityConfig extend WebSecurityConfigurerAdapter{
    //注入PasswordEncoder类到spring容器中
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswwordEncoder();
    }

    @Autowired
    private UserDetailsService userDetailsService;
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.formLogin()  //自定义自己编写的登录页面
            .loginPage("/login.html")  //登录页面设置
            .loginProcessingUrl("/user/login")  //登录访问路径
            .defaultSuccessUrl("/test/index").permitAll()  //登陆成功后,跳转路径
            .and().authorizeRequests()
                .antMatchers("/","/test/hello","/user/login").permitAll()  //设置哪些路径可以直接访问,不需要认证
            //当前登录用户,只有具有admins权限才可以访问这个路径
            //.antMatchers("/test/index").hasAuthority("role")
            .antMatchers("/test/index").hasAnyAuthority("role,admin")
            .anyRequest().authenticated()
            .and().csrf().disable();  //关闭csrf防护
    }
}

        第二步:在UserDetailsService,把返回User对象设置权限(上述UserDetalsService中,如下图就是):

        3)hasRole方法

        如果用户具备给定角色就允许访问,否则出现403;如果当前主体具有指定的角色,则返回true。

        源码中会自动添加一个前缀 “ROLE_”,所以在给对象设置权限时,要在前面加前缀 “ROLE_”。

        4)hasAnyRole方法

        表示用户具备任何一个条件都可以访问。

        第一步:给用户添加角色。

        第二步:修改配置文件。

4.5、自定义403没有权限访问页面

        在配置类进行配置:

        

 4.6、注解使用

        1)@Secured

        判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_”,使用注解先要开启注解功能。

        第一步:在启动类上添加注解 @EnableGlobalMethodSecurity(securedEnabled = true)来实现开启注解功能。

        第二步:在控制器 (controller) 方法上添加注解。

@Secured({"ROLE_normal","ROLE_admin"})

        第三步:在userDetailsService设置用户角色 

        2) @PreAuthorize

        注解适合进入方法前的权限验证,@PreAuthorize可以将登录用户的roles/permissions参数传到方法中。

        第一步:先开启注解功能:@EnableGlobalMethodSecurity(prePostEnabled = true)

        第二步:在控制器(controller)方法上添加注解。

@PreAuthorize("hasAnyAuthority('menu:system')")

        3) @PostAuthorize

        注解使用不多,在方法执行后再进行权限验证,适合验证带有返回值的权限。

        第一步:先开启注解功能:@EnableGlobalMethodSecurity(prePostEnabled = true)。

        第二步:在控制器(controller)方法上添加注解。

@PostAuthorize("hasAnyAuthority('menu:system')")

        4)@PostFilter

        对方法返回数据做过滤。

        @PostFilter:权限验证之后对数据进行过滤,留下用户名是admin1的数据。

        表达式中的filterObject引用的是方法返回值List中的某一个元素。

@RequestMapping("getAll")
@PreAuthorize("hasRole('ROLE_管理员')")
@PostFilter("filterObject.username == 'admin'")
@ResponseBody
public List<UserInfo> getAllUser(){
    
    ArrayList<UserInfo> list = new ArrayList<>();
    list.add(new UserInfo(11,"admin1","6666"));
    list.add(new UserInfo(21,"admin2"."888"));
    return list;
}

        5)@PreFilter

        进入控制器之前对数据进行过滤。(对传入的参数进行过滤)

@RequestMapping("getTestPreFilter")
@PreAuthorize("hasRole('ROLE_管理员')")
@PreFilter(value = "filterObject.id % 2 == 0")
@ResponseBody
public List<UserInfo> getTestPreFilter( @RequestBody List<UserInfo> list){

    list.forEach(t-> {
        sout(t.getId() + "\t" + t.getUsername());
    });
    return list;
}

4.7、用户注销

        在配置类中添加退出的配置。

Protected void configure(HttpSecurity http) throws Exception{

    //退出
    http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
}

4.8、基于数据库实现自动登录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值