Spring Security 入门(一)

一、spring security讲解:
Spring Security是spring提供的一种基于 Spring AOP 和 Servlet 过滤器的安全框架,其提供了对网页端请求级和方法调用级的处理身份认证(用户身份认证)和授权(登录的用户拥有什么权限)。Spring Security的核心是一组过滤器链,在springboot中引入依赖即默认对多有接口启动了安全管理,其中最核心的就是用来认证用户的身份的Basic Authentication Filter 。
但由于在传统MVC框架下整合 Spring Security需要大量配置,比较麻烦,所以大部分人偏向于使用功能没它强大的Shiro 。然而springboot的出现使其又回到人们的视野,springboot对Spring Security 提供了 自动化配置,达到了不用配置即可使用 Spring Security。

二、使用(spring boot 基础上,springboot2.2.5)

1、引入Spring Security依赖(只要加入依赖,项目的所有接口都会被自动保护起来,即默认都要登录认证):

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-security</artifactId> 
</dependency>

2、我们写一个测试的controller接口

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }
}

在没加入依赖前,项目启动即可访问该接口。加入Spring Security依赖后访问该接口(返回 302 响应码),默认重定向到 localhost:8080/login 的登录页地址(该登录页默认含有一个form表单,表单含有用户名属性:username,密码属性:password,表单提交的url为 /login),输入用户名 user、密码(服务启动日志查看)登录后才会重新跳到访问的 /hello 接口地址。

3、默认登录账号说明

上面讲到默认登录的账号为user,我们也可以自己设置账号信息(三种方式)。

  • 在配置文件添加用户、密码信息
  • 编写代码继承 WebSecurityConfigurerAdapter 类,在该类中配置用户信息,将会把信息存到内存
  • 用户信息配置在数据库,从数据库获取(真正生产上使用的方式)

(1)配置文件

spring.security.user.name=testname
spring.security.user.password=testpass
#springboot 1.xx 使用如下配置
#security.user.name=testname
#security.user.password=testpass

(2)代码继承 WebSecurityConfigurerAdapter 类

@Configuration
//利用@EnableWebSecurity 注解继承 WebSecurityConfigurerAdapter的类,这样就构成了 Spring Security 的配置
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //下面这两行配置表示在内存中配置了两个用户,这里两个用户的密码到是123456,在springboot1.xx版本密码设置不用加{noop}
        auth.inMemoryAuthentication()
                // testadmin同时具有 ADMIN,USER权限,testuser只有USER权限(大写)
                .withUser("testadmin").password("{noop}123456").roles("ADMIN","USER")
                .and()
                .withUser("testuser").password("{noop}123456").roles("USER");
    }
}

(3)数据库的配置等后面讲

4、在SecurityConfig类中添加不同用户能访问的权限资源(特定资源只能由特定角色访问)

因为上面用户配置中,testadmin拥有ADMIN、USER两个权限,所以可以访问任何资源;testuser只有USER权限,只能访问/user/** 对应的接口

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()   //开启登录配置
                //表示/user开头的接口拥有USER权限才能访问
                .antMatchers("/user/**").hasRole("USER") 
                //表示/admin开头的接口拥有ADMIN权限才能访问
                .antMatchers("/admin/**").hasRole("ADMIN") 
                //表示剩余的其他接口,登录之后就能访问,不涉及权限问题
                .anyRequest().authenticated()  
                .and()
                //指定支持基于表单的身份验证,当需要用户登录时候,转到默认的登录页面。可以通过loginPage(String)指定自定义的登录页面
                .formLogin()
                .and()
                //开启http basic
                .httpBasic();
    }

5、在SecurityConfig类中添加放行静态资源

下面定义 /filter 接口,/js/**、/css/**的静态资源不需要登录即可访问

    @Override
    public void configure(WebSecurity web) throws Exception {
        //某一个请求地址不需要拦截的话,直接过滤掉该地址,即该地址不走 Spring Security 过滤器链,主要一些静态资源
        web.ignoring().antMatchers("/filter", "/js/**", "/css/**");
    }

6、最终 SecurityConfig类

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("testadmin").password("{noop}123456").roles("ADMIN","USER")
                .and()
                .withUser("testuser").password("{noop}123456").roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/user/**").hasRole("USER")
                .antMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .formLogin().and()
                .httpBasic();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/filter", "/js/**", "/css/**");
    }
}

7、controller接口

@RestController
@RequestMapping("/user")
public class UserController {
    @GetMapping("/hello")
    public String userHasRole(){
        return "user";
    }
}

@RestController
@RequestMapping("/admin")
public class AdminController {
    @GetMapping("/hello")
    public String adminHasRole(){
        return "admin";
    }
}

8、通过 SecurityContextHolder 获取登录的用户信息

在接口中,可以通过SecurityContextHolder 类来获取当前登录并调用该接口的用户信息

    @GetMapping("/hello")
    public String hello(){
        String username = "";
        Object principl = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if(principl instanceof UserDetails) {
            username = ((UserDetails)principl).getUsername();
        }else {
            username = principl.toString();
        }
        System.out.println("当前登录用户为:" + username);
        return "hello";
    }

9、另外我们还可以直接在controller的接口上定义用户需要的角色权限(注意类上@EnableGlobalMethodSecurity(prePostEnable = true) 注解才能支持)

角色设置一定要ROLE_前缀,这是security定义的,可以在源码(RoleVote.java)看到。

  • @PreAuthorize :接口方法调用前做认证
  • @PostAuthorize :接口方法调用后做认证
  • @PreFilter :主要用于接口调用前对List参数做验证
  • @PostFilter:主要用于接口返回的List做验证
@RestController
@RequestMapping("/admin")
@EnableGlobalMethodSecurity(prePostEnable = true)
public class AdminController {
    @GetMapping("/hello")
    @PreAuthorize("hasRole('ROLE_ADMIN')")  //表示访问该接口需要ADMIN角色,一定要ROLE_前缀
    public String adminHasRole(){
        return "admin";
    }

    @GetMapping("/hello1")
    //可以加多个角色,另外还要很多hasxxx方法等
    @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')") 
    public String adminHasRole1(){
        return "admin";
    }

    @GetMapping("/hello2")
    //可以对请求参数做处理,传入参数id要大于1,且name为当前登录用户,不符合则抛异常
    @PreAuthorize("#id>1 and principal.username.equals(#name)") 
    public String adminHasRole2(Integer id,String name){
        return "admin";
    }

    @GetMapping("/hello3")
    //返回的字符串为122,不是则抛异常
    @PostAuthorize("hasRole('ROLE_ADMIN') and returnObject.equals('122')") 
    public String adminHasRole3(Integer id){
        return "122";
    }

    @GetMapping("/hello4")
    //传入的集合个数是否为2的倍数,返回的集合个数是否为4的倍数
    @PreFilter("filterObject%2==0")
    @PostFilter("filterObject%4==0")
    public String adminHasRole4(List<String> list){
        return list;
    }
}

10、附加对 SecurityConfig 类中配置权限规则的方法进行更多说明

  • permitAll:永远为true,即都能访问
  • denyAll:永远为false,不对外访问
  • anonymous:用户为匿名时可访问
  • rememberMe:用户为rememberMe能访问
  • authenticated:不是匿名也不是rememberMe才能访问
  • hasRole(role):用户有指定角色权限才能访问
  • hasAnyRole([role1,role2]):有任一角色权限能访问
  • hasAuthority(authority):有指定权限时能访问,如 hasAuthority("read"),基本通hasRole
  • hasAnyAuthority([authority1,authority2]):有任意一个指定权限
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()  //开启登录配置
                // /a,/b接口不需要任何身份验证,permitAll表示放行
                .antMatchers("/a","/b").permitAll()
                //表示/user/*请求中的GET请求才会到这里,同时可以通过access指定多个权限条件
                .antMatchers(HttpMethod.GET,"/user/*").access("hasRole('ADMIN') and hasAnyAuthority('xx')")
                //表示剩余的其他接口,登录之后就能访问,authenticated表示需要登录认证,放在其他权限设置后
                .anyRequest().authenticated()
                //每一个放行/拦截描述之后加and才能继续描述
                .and()
                //指定支持基于表单的身份验证。
                .formLogin()
                //定义登录页面,未登录时,访问一个需要登录之后才能访问的接口,会自动重定向到该请求
                .loginPage("/login.html")
                //登录处理接口,默认登录页面表单提交的接口为/login,我们如果自己写login页面,则这里要修改为login页面表单登录对应的url,但url接口不用我们提供
                .loginProcessingUrl("/doLogin")
                //定义登录时用户名的 key,默认登录页面表单的属性为 username
                .usernameParameter("name")
                //定义登录时用户密码的 key,默认登录页面表单的属性为 password
                .passwordParameter("pass")
                // successHandler 方法中,配置登录成功的回调
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                        //httpServletResponse.setContentType("application/json;charset=utf-8");
                        //httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authentication)); //返回json给调用方
                        System.out.println("success");
                    }
                })
                //登录失败回调
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                        System.out.println("fail");
                    }
                })
                //和上面表示的表单登录相关的接口统统都直接通过
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/logout")
                //logoutSuccessHandler 中则配置注销成功的回调
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override
                    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                        System.out.println("logout success");
                    }
                })
                .permitAll()
                .and()
                .httpBasic()
                .and()
                //使用WebSecurityConfigurerAdapter时,默认启用CSRF,这里关闭
                .csrf().disable();
        //配置基于x509的认证
        http.x509();
    }

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值