SpringSecurity入门学习(1)

目录

SpringSecurity简介

SpringSecurity初体验

1.新建项目

2.添加测试内容

3.启动测试

4.配置登录用户

SpringSecurity配置自定义登录页面

1.配置多个登录用户

2.配置自己的登录页面

SpringSecurity前后端分离

1.登陆处理

2.注销处理

3.未登录处理

4.编程测试

SpringSecurity授权

1.给用户添加角色

2.准备测试资源

3.给资源配置访问权限

4.测试

5.权限继承(角色继承)

6.无权限(403 Forbidden)处理


SpringSecurity简介

SpringSecurity是一个强大的可高度定制的认证和授权框架,对于Spring应用来说它是一套Web安全标准。

SpringSecurity注重于为Java应用提供认证和授权功能,像所有的Spring项目一样,它对自定义需求具有强大的扩展性。

 

SpringSecurity初体验

1.新建项目

使用IDEA创建SpringBoot项目,引入SpringSecurity和web依赖

若不是IDEA,在新建的SpringBoot项目依赖中添加如下:

<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>

2.添加测试内容

新建测试controller类

@RestController
public class TestController {
    @GetMapping("/test")
    public String test() {
        return "Hello World!";
    }
    @GetMapping("/success")
    public String success() {
        return "success!";
    }
    @GetMapping("/failure")
    public String failure() {
        return "failure!";
    }
}

3.启动测试

直接启动项目,启动之后,我们在控制台中看见SpringSecurity生成的一个UUID登录密码,登录用户默认的是user

Using generated security password: 6de83077-9657-4f8d-92d3-d1e651cf34a4

至于为什么默认的登录用户时user,我们可以进入SecurityProperties类中查看Security默认配置信息,如下:

//SecurityProperties中内部类User部分源码
private String name = "user";
private String password = UUID.randomUUID().toString();
private boolean passwordGenerated = true;//是否生成密码

然后我们通过浏览器访问:http://localhost:8080/test ,页面会自动重定向到默认的登录页面

输入用户名/密码,点击登录,之后访问成功

4.配置登录用户

由于SpringSecurity默认的登录用户密码在每次启动时都会重新生成,我们可以再配置文件中配置固定的用户名和密码

spring.security.user.name=symon
spring.security.user.password=123456

然后重新启动,就能使用自定义的用户名和密码登录

自此一个简单的Security集成就结束了,接下来我们开始入门学习Security的其他配置

SpringSecurity配置自定义登录页面

1.配置多个登录用户

在上面的基础上,新建SecurityConfig配置类,并继承WebSecurityConfigurerAdapter ,重写其中configure方法即可进行自定义配置,下面是个简单配置多个登录用户:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("symon").password("1234567").roles("root")
                .and().withUser("test").password("1234567").roles("user");
    }
    @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

其中:

      passwordEncoder()方法配置密码加密方式,这里为了方便采用了NoOpPasswordEncoder不加密配置

      configure(AuthenticationManagerBuilder auth)方法中配置了inMemoryAuthentication在内容中定义用户,使用and进行连接配置多个用户

再次启动项目,访问/test接口,使用代码中配置的用户名密码进行登录即可登录成功,此时我们发现代码中的配置会覆盖掉properties配置文件中的配置

2.配置自己的登录页面

一般,在项目中我们不会使用Security默认的登录页面,所需我们需要在登陆时配置自己写的登录页面

继续在SecurityConfig重写configure(HttpSecurity http)方法,configure(WebSecurity web)方法:

@Override
public void configure(WebSecurity web) throws Exception {
    //用来忽略URL地址,被忽略的URL不会被Security拦截,一般项目中的静态文件需要忽略。
    web.ignoring().antMatchers("/js/**", "/css/**","/images/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login.html") //设置登录页面
            .loginProcessingUrl("/login") //设置登录请求接口
            .usernameParameter("name") //配置登录用户参数名,默认值username
            .passwordParameter("pass") //配置登录密码参数名,默认值password
            .defaultSuccessUrl("/success", true) //配置登录成功跳转地址,重定向
            .failureUrl("/failure") //配置登录失败跳转地址,重定向
            .permitAll() //允许以上页面和接口,不被拦截
            .and()
            .logout()
            .logoutUrl("/logout")
            .logoutSuccessUrl("/login.html") //配置登出成功跳转地址
            .deleteCookies() //清除cookie
            .clearAuthentication(true) //清除认证信息 默认true
            .invalidateHttpSession(true) //使HttpSession失效 默认true
            .permitAll()
            .and()
            .csrf().disable();
}

相关配置解释已在注释中

补充:

      successForwardUrl(),failureForwardUrl()也可进行成功和失败跳转,不过该方式是转发

然后将自己写的登录页面放在resources/static文件夹下,我这里写了一个简单的登录页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
<form action="/login" method="post">
    <div>
        <label for="name">用户名</label>
        <input type="text" name="name" id="name">
    </div>
    <div>
        <label for="pass">密码</label>
        <input type="password" name="pass" id="pass">
    </div>
    <div>
        <button type="submit">
            <span>登录</span>
        </button>
    </div>
</form>
</body>
</html>

启动项目之后,浏览器访问/test接口,就会重定向到自己的登录页面,输入用户名密码即可登录成功

SpringSecurity前后端分离

在前后端分离的今天,我们逐渐采用JSON进行数据交互,下面我们开始学习基于 session 的前后端分离认证。

1.登陆处理

之前登录成功或失败是通过defaultSuccessUrl和failureUrl来重定向跳转页面,前后端分离之后,无论登录成功或失败服务端都只返回JSON信息

登录成功处理

successHandler是登录成功的处理

.successHandler((req, resp, authentication) -> {
    resp.setContentType(CONTENT_TYPE);
    PrintWriter out = resp.getWriter();
    out.write(JSON.toJSONString(ResponseDTO.success("登录成功!")));
    out.flush();
    out.close();
})

其中:

      successHandler 方法的参数是一个 AuthenticationSuccessHandler 对象,实现其 onAuthenticationSuccess方法即可进行登录成功的操作

      onAuthenticationSuccess 方法有三个参数,分别是:HttpServletRequest、HttpServletResponse、Authentication

      HttpServletRequest和HttpServletResponse可以用来做服务端和客户端跳转,HttpServletResponse也可用来返回 JSON 数据, Authentication包含登录用户的认证信息

登陆失败处理

failureHandler是登录失败的处理

.failureHandler((req, resp, exception) -> {
    resp.setContentType(CONTENT_TYPE);
    PrintWriter out = resp.getWriter();
    String exeMsg = "登录失败!";
    if (exception instanceof LockedException) {
        exeMsg = "账户已被锁定!";
    } else if (exception instanceof CredentialsExpiredException) {
        exeMsg = "密码已过期!";
    } else if (exception instanceof AccountExpiredException) {
        exeMsg = "账户已过期!";
    } else if (exception instanceof DisabledException) {
        exeMsg = "账户已被禁用!";
    } else if (exception instanceof BadCredentialsException) {
        exeMsg = "用户名或者密码输入错误,请重新输入!";
    }
    out.write(JSON.toJSONString(ResponseDTO.error(exeMsg)));
    out.flush();
    out.close();
})

其中:

      failureHandler方法的参数是一个AuthenticationFailureHandler对象,实现其onAuthenticationFailure方法即可进行登录失败的操作

      onAuthenticationFailure与登录成功参数类似,不过第三个参数是Exception异常对象,根据异常类型,进行不同的处理

2.注销处理

同样的注销也有类似的回调,logoutSuccessHandler用来处理注销成功的操作,参数和登录类似

.logoutSuccessHandler((req, resp, auth) -> {
    resp.setContentType(CONTENT_TYPE);
    PrintWriter out = resp.getWriter();
    out.write(JSON.toJSONString(ResponseDTO.success("注销成功!再见!")));
    out.flush();
    out.close();
})

3.未登录处理

如果用户没有登录就访问一个需要认证后才能访问的页面,这个时候,我们需要给前端一个未登录的提醒,让前端来进行页面跳转

.exceptionHandling()
.authenticationEntryPoint((req, resp, exception) -> {
    resp.setContentType(CONTENT_TYPE);
    PrintWriter out = resp.getWriter();
    out.write(JSON.toJSONString(ResponseDTO.unauthenticated("未登录,请重新登录!")));
    out.flush();
    out.close();
})

接下来开始实操

 

4.编程测试

首先我们新建一个项目,除了要引入security和web依赖之外,这里引入了lombok和fastjson方便操作

<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.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.70</version>
</dependency>

创建SecurityConfig配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    public static final String CONTENT_TYPE = "application/json;charset=utf-8";
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .successHandler((req, resp, auth) -> {
                    resp.setContentType(CONTENT_TYPE);
                    PrintWriter out = resp.getWriter();
                    out.write(JSON.toJSONString(ResponseDTO.success("登录成功!")));
                    out.flush();
                    out.close();
                })
                .failureHandler((req, resp, exception) -> {
                    resp.setContentType(CONTENT_TYPE);
                    PrintWriter out = resp.getWriter();
                    String exeMsg = "登录失败!";
                    if (exception instanceof LockedException) {
                        exeMsg = "账户已被锁定!";
                    } else if (exception instanceof CredentialsExpiredException) {
                        exeMsg = "密码已过期!";
                    } else if (exception instanceof AccountExpiredException) {
                        exeMsg = "账户已过期!";
                    } else if (exception instanceof DisabledException) {
                        exeMsg = "账户已被禁用!";
                    } else if (exception instanceof BadCredentialsException) {
                        exeMsg = "用户名或者密码输入错误,请重新输入!";
                    }
                    out.write(JSON.toJSONString(ResponseDTO.error(exeMsg)));
                    out.flush();
                    out.close();
                })
                .and()
                .logout()
                .logoutSuccessHandler((req, resp, auth) -> {
                    resp.setContentType(CONTENT_TYPE);
                    PrintWriter out = resp.getWriter();
                    out.write(JSON.toJSONString(ResponseDTO.success("注销成功!再见!")));
                    out.flush();
                    out.close();
                })
                .permitAll()
                .and()
                .exceptionHandling()
                .authenticationEntryPoint((req, resp, exception) -> {
                    resp.setContentType(CONTENT_TYPE);
                    PrintWriter out = resp.getWriter();
                    out.write(JSON.toJSONString(ResponseDTO.unauthenticated("未登录,请重新登录!")));
                    out.flush();
                    out.close();
                })
                .and().csrf().disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("symon").password("1234567").roles("root")
                .and().withUser("test").password("1234567").roles("user");
    }
    @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

其中:

      ResponseDTO是我自定义的返回类,其中的字段如下,根据自己需要创建

    private Integer code;
    private String msg;
    private T data;

然后新建测试接口,启动项目使用postman来测试

@GetMapping("/test")
public String test() {
    return "Hello World!";
}

当直接访问/test接口时,会返回未登录信息

调用登录接口,输入正确用户名密码,返回登录成功信息

调用登录接口,输入错误用户名密码,返回登录失败信息

登录成功之后,再访问/test测试接口,即可访问成功

调用注销接口,返回注销成功信息

讲完了,security前后端认证的相关操作,接下来我们说一下security的授权

SpringSecurity授权

所谓的授权,就是用户如果要访问某一个资源,我们要去检查用户是否具备这样的权限,如果具备就允许访问,如果不具备,则不允许访问

下面我们在前面认证的代码基础上进行实操

1.给用户添加角色

在前面的配置中,基于内存来定义用户,其中roles()方法就是用来给用户添加角色

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("symon").password("1234567").roles("root")
                .and().withUser("test").password("1234567").roles("user");
    }

2.准备测试资源

新建两个测试接口,代表两种访问资源

@GetMapping("/user/test")
public String user(){
    return "user权限";
}
@GetMapping("/root/test")
public String root(){
    return "root权限";
}

3.给资源配置访问权限

.antMatchers("/user/**").hasRole("user")
.antMatchers("/root/**").hasRole("root")

anyRequest 一定要配置在antMatchers之后,否则启动报错

antMatchers中通配符的含义

      * * 匹配多层url路径

      * 匹配一层url路径

      ? 匹配任意单个字符

4.测试

启动项目测试,使用symon登录,分别访问 /test,/root/test以及 /user/test三个接口,结果如下:

      /test 没有配置访问权限,访问成功。

      /root/test 需要root权限,访问成功。

      /user/test 需要user权限,访问失败。

这里就不截图了,可以自己测试效果;再使用test登录,对比两个用户访问的结果

5.权限继承(角色继承)

在实际开发中,user角色能够访问的资源,root角色也是能够访问,所以我们就要用到角色继承

在配置类中填加如下配置:

@Bean
RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
    hierarchy.setHierarchy("ROLE_root > ROLE_user");
    return hierarchy;
}

重启项目,依然使用symon登录并访问/user/test,我们发现就可以访问成功

6.无权限(403 Forbidden)处理

在上面的测试中,我们发现在访问一个,无权访问的接口时,返回的结果如下:

{
  "timestamp": "2020-07-20T14:10:33.331+00:00",
  "status": 403,
  "error": "Forbidden",
  "message": "",
  "path": "/root/test"
}

但是这个结果并不是我们想要的JSON数据格式

前面认证时我们定义了自己的返回JSON格式,其中在未登录处理的配置时,我们还可以进行无权限返回配置,配置和未登录处理配置类似:

.accessDeniedHandler((req, resp, exception) -> {
    resp.setContentType(CONTENT_TYPE);
    PrintWriter out = resp.getWriter();
    out.write(JSON.toJSONString(ResponseDTO.unauthorized("无权限!")));
    out.flush();
    out.close();
})

添加配置之后,重启项目

当访问登录用户无权访问的资源时,即可返回自己的JSON格式

{
  "code": 403,
  "msg": "无权限!"
}

后面我们将继续学习Security

 

 

 

 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值