从零玩转SpringSecurity+JWT整合前后端分离2

简介: 从零玩转SpringSecurity+JWT整合前后端分离

2.Spring Security 配置多用户认证

概述

认证就是登陆,我们现在没有连接数据库,那么我们可以模拟下用户名和密码

/**
 * @Author 杨不易呀
 * web 安全的配置类
 * <p>
 * WebSecurityConfigurerAdapter   web安全配置的适配器
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter 
{
    /**
     * 配置认证(用户)管理 模拟内存用户数据
 * 重点说明:
 * 在开发中,我们一般只针对权限,很少去使用角色
 * 后面的讲解中我们以权限为主也就是 authorities 这里面的东西
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 在内存中创建了两个用户
        // 注意点: 我们添加了安全配置类,那么我们在 yml 里面的用户密码配置就失效了
        auth.inMemoryAuthentication()
                .withUser("yby") // 用户名
                .password("yby") // 密码 
                .roles("ADMIN_yby") // 给了一个角色
                .authorities("sys:add", "sys:update", "sys:delete", "sys:select") 
                // 注意点:给yby用户四个权限 如果权限和角色都给了 那么角色就失效了
                .and()
                .withUser("test")
                .password("test")
                .roles("TEST")
                .authorities("sys:select") // 加了一个权限
        ;
    }
}

1.启动测试

使用yby/yby登录访问 可以发现 控制台报错了

image-20210408172056590-6386c2e064aa4eb487f2510daaf9ebae.png

这个是因为 spring Sercurity 强制要使用密码加密,当然我们也可以不加密,但是官方要求是不 管你是否加密,都必须配置一个类似 Shiro 的凭证匹配器

2.添加密码加密器

/**
     * 给容器中放一个加密器 springSecurity5.x之后推荐使用加密
     * 也可以不给加密
     * new NoOpPasswordEncoder()
     * 这个加密器对同一个值加密后 会得到不同的结果
     * 只要是用同一个加密器加密的 解密也是一样的
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

3.修改用户配置

// 在内存中创建了两个用户
 // 注意点: 我们添加了安全配置类,那么我们在 yml 里面的用户密码配置就失效了
        auth.inMemoryAuthentication()
                .withUser("yby") // 用户名
                .password(passwordEncoder().encode("yby") // 密码 
                .roles("ADMIN_yby") // 给了一个角色
                .authorities("sys:add", "sys:update", "sys:delete", "sys:select") 
                // 注意点:给yby用户四个权限 如果权限和角色都给了 那么角色就失效了
                .and()
                .withUser("test")
                .password(passwordEncoder().encode("test")
                .roles("TEST")
                .authorities("sys:select")
        ;

4.重启测试

两个用户都可以登录成功l了 恭喜恭喜!!!!

5.测试加密和解密 demo

public class TestPasswordEncoder {
public static void main(String[] args) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encode1 = passwordEncoder.encode("123");
System.out.println(encode1);
String encode2 = passwordEncoder.encode("123");
System.out.println(encode2);
String encode3 = passwordEncoder.encode("123");
System.out.println(encode3);
// 查看加密后是否匹配
System.out.println(passwordEncoder.matches("123", encode1));
System.out.println(passwordEncoder.matches("123", encode2));
System.out.println(passwordEncoder.matches("123", encode3));
}
}

查看控制台发现特点是:相同的字符串加密之后的结果都不一样,但是比较的时候是一样的,这个 算法比 shiro 的 MD5 好用,不用自己在数据库去存盐了image-20210408172622024-8d7672fe222f4c71a4210b618a0a2777.png

3.如何获取当前登录用户的信息(两种方式)

1.往HelloController添加请求

/**
* 获取当前用户信息,直接在参数中注入 Principal 对象
* 此对象是登录后自动写入 UsernamePasswordAuthenticationToken 类中
*
* @param principal
* @return
*/
@GetMapping("userInfo")
public Principal getUserInfo(Principal principal) {
return principal;
}
/**
* SecurityContextHolder.getContext()获取安全上下文对象
* 就是那个保存在 ThreadLocal 里面的安全上下文对象
* 总是不为 null(如果不存在,则创建一个 authentication 属性为 null 的 empty 安全上下文对象)
* 获取当前认证了的 principal(当事人),或者 request token (令牌)
* 如果没有认证,会是 null,该例子是认证之后的情况
*/
@GetMapping("userInfo2")
public Object getUserInfo2() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
ret

2.请求任意一个都可以获取到登陆后的json信息

image-20210408174013135-24a6d80b29784db2b2ba1e2a0df1bd57.png

4.Spring Security 用户,角色,权限拦截配置讲解

1.角色和权限的配置,修改 WebSecurityConfig 类

/**
     * 配置认证(用户)管理 模拟内存用户数据
     * 重点说明:
     * 在开发中,我们一般只针对权限,很少去使用角色
     * 后面的讲解中我们以权限为主也就是 authorities 这里面的东西
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 在内存中创建了两个用户
        auth.inMemoryAuthentication()
                .withUser("yby") // 用户名
                .password(passwordEncoder().encode("yby")) // 密码 需要加密
                .roles("ADMIN_SXT") // 给了一个角色
                .authorities("sys:add", "sys:update", "sys:delete", "sys:select") // 给yby用户四个权限 如果权限和角色都给了 那么角色就失效了
                // 测试用户 只有一个查看权限
                .and()
                .withUser("test")
                .password(passwordEncoder().encode("test"))
                .roles("TEST") // 失效 因为有 authorities这个了
                .authorities("sys:select")
                // admin用户 角色权限区分
                .and()
                .withUser("admin")
                .password(passwordEncoder().encode("admin"))
                .roles("ADMIN") // 注意点:不能带入前缀ROLE_ security里面默认会添加的 最终结果是  ROLE_ADMIN
        ;
    }
/**
* 配置 http 请求验证等
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
    // 注释掉他自己的方法 走我们自己的
    // super.configure(http);
    // 给一个表单登陆 就是我们的登录页面,登录成功或者失败后走我们的 url
    http.formLogin()
        .successForwardUrl("/welcome") // 登录成功走的url
        .failureForwardUrl("/fail") // 登录失败走的url
        .permitAll();
    // 匹配哪些 url,需要哪些权限才可以访问 当然我们也可以使用链式编程的方式
    http.authorizeRequests()
        .antMatchers("/query").hasAuthority("sys:query") // 表示这个用户有这个权限标识才能访问
        .antMatchers("/save").hasAuthority("sys:save")
        .antMatchers("/del").hasAuthority("sys:del")
        .antMatchers("/update").hasAuthority("sys:update")
        .antMatchers("/admin/**").hasRole("ADMIN") // 表示这个用户有这个角色才能访问
    ; // 其他所有的请求都需要登录才能进行
    // 所有的请求都需要认证才可以访问
    http.authorizeRequests().anyRequest().authenticated();
}

2.创建AuthorityController 演示权限访问

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @Author 杨不易呀
 */
@RestController
public class AuthorityController {
    /**
     * 登录成功的主页返回值
     *
     * @return
     */
    @PostMapping("welcome")
    public String welcome() {
        return "欢迎来到主页";
    }
    /**
     * 登录失败的返回值
     *
     * @return
     */
    @PostMapping("fail")
    public String fail() {
        return "登录失败了";
    }
    /**
     * 开启方法权限的注解
     *
     * @return
     */
    @GetMapping("add")
    public String add() {
        return "欢迎来到主ADD";
    }
    @GetMapping("update")
    public String update() {
        return "欢迎来到UPDATE";
    }
    @GetMapping("delete")
    public String delete() {
        return "欢迎来到DELETE";
    }
    @GetMapping("select")
    public String select() {
        return "欢迎来到SELECT";
    }
    @GetMapping("role")
    public String role() {
        return "欢迎来到ROLE";
    }
    @GetMapping("admin/hello")
    public String admin() {
    return "我是只有 admin 角色才可以访问的";
    }
}

3.创建访问403权限不足页面.html

image-20210408175450877-067a6d0be9ff4938adece07813059668.png

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>没有访问权限哦</title>
</head>
<body>
<h1 style="color: red">您没有访问权限</h1>
</body>
</html>

会自动的跳转到该目录下.

访问该用户没有的权限请求

image-20210408175834209-972b7e1ceed94d1eba41545bb9cdcc64.png

5.Spring Security 返回 JSON(前后端分离)

在上面的例子中,我们返回的是 403 页面,但是在开发中,如 RestAPI 风格的数据,是不能返回一 个页面,而应该是给一个 json

1.添加处理器 RestAuthorizationAccessDeniedHandler

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
/**
 * @Author 杨不易呀
 * 自定义登录成功的处理器
 * 返回json
 */
@Configuration
public class AuthenticateSuccess implements AuthenticationSuccessHandler {
    /**
     * 登陆成功后执行的处理器
     *
     * @param request
     * @param response
     * @param authentication
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        System.out.println("登录成功了");
        // 把json串写出去
        response.setContentType("application/json;charset=utf-8");
        HashMap<String, Object> map = new HashMap<>(8);
        map.put("code", 200);
        map.put("msg", "登录成功");
        // 把用户信息返回给前端 让前端可以保存起来
        map.put("data", authentication);
        ObjectMapper objectMapper = new ObjectMapper();
        String s = objectMapper.writeValueAsString(map);
        // 写出去
        PrintWriter writer = response.getWriter();
        writer.write(s);
        // 刷新流 关闭流
        writer.flush();
        writer.close();
    }
}

2.修改WebSecurityConfig配置文件

/**
* 将自定义的拒绝访问处理器注入进来
*/
@Autowired
private AccessDeniedHandler accessDeniedHandler;
/**
* 配置 http 请求验证等
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
    // 自定义403请求返回json
    http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());
    // 给一个表单登陆 就是我们的登录页面,登录成功或者失败后走我们的 url
    http.formLogin()
        .successForwardUrl("/welcome") // 登录成功走的url
        .failureForwardUrl("/fail") // 登录失败走的url
        .permitAll();
    // 匹配哪些 url,需要哪些权限才可以访问 当然我们也可以使用链式编程的方式
    http.authorizeRequests()
        .antMatchers("/query").hasAuthority("sys:query") // 表示这个用户有这个权限标识才能访问
        .antMatchers("/add").hasAuthority("sys:add")
        .antMatchers("/delete").hasAuthority("sys:delete")
        .antMatchers("/update").hasAuthority("sys:update")
        .antMatchers("/admin/**").hasRole("ADMIN") // 表示这个用户有这个角色才能访问
    ; // 其他所有的请求都需要登录才能进行
    // 所有的请求都需要认证才可以访问
    http.authorizeRequests().anyRequest().authenticated();
}

3.重新启动访问用户没有权限的url下·

image-20210408180438771-e3450788780d46ffacacbb63af5b4248.png

4.登录成功或者失败都返回 JSON,我们需要自定义处理器

创建 AuthenticateSuccess

登录成功返回json

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
/**
 * @Author 杨不易呀
 * 自定义登录成功的处理器
 * 返回json
 */
@Configuration
public class AuthenticateSuccess implements AuthenticationSuccessHandler {
    /**
     * 登陆成功后执行的处理器
     *
     * @param request
     * @param response
     * @param authentication
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        System.out.println("登录成功了");
        // 把json串写出去
        response.setContentType("application/json;charset=utf-8");
        HashMap<String, Object> map = new HashMap<>(8);
        map.put("code", 200);
        map.put("msg", "登录成功");
        // 把用户信息返回给前端 让前端可以保存起来
        map.put("data", authentication);
        ObjectMapper objectMapper = new ObjectMapper();
        String s = objectMapper.writeValueAsString(map);
        // 写出去
        PrintWriter writer = response.getWriter();
        writer.write(s);
        // 刷新流 关闭流
        writer.flush();
        writer.close();
    }
}

修改配置 http 请求验证

注入登录失败和登录成功返回json

/**
     * 自定义登录成功返回json
     */
 @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;
/**
* 配置 http 请求验证等
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
    // 给一个表单登陆 就是我们的登录页面,登录成功或者失败后走我们的 url
    //http.formLogin()
       // .successForwardUrl("/welcome") // 登录成功走的url
        //.failureForwardUrl("/fail") // 登录失败走的url
       // .permitAll();
    // 这里使用了前后端分离的模式 实现我们的登录成功和失败返回json
    http.formLogin()
        .successHandler(authenticationSuccessHandler)
        .failureHandler(authenticationFailureHandler());
    // 匹配哪些 url,需要哪些权限才可以访问 当然我们也可以使用链式编程的方式
    http.authorizeRequests()
        .antMatchers("/query").hasAuthority("sys:query") // 表示这个用户有这个权限标识才能访问
        .antMatchers("/save").hasAuthority("sys:save")
        .antMatchers("/del").hasAuthority("sys:del")
        .antMatchers("/update").hasAuthority("sys:update")
        .antMatchers("/admin/**").hasRole("ADMIN") // 表示这个用户有这个角色才能访问
    ; // 其他所有的请求都需要登录才能进行
    // 所有的请求都需要认证才可以访问
    http.authorizeRequests().anyRequest().authenticated();
}
/**
     * 登录失败的处理器
     *
     * @return
     */
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return (request, response, exception) -> {
            response.setContentType("application/json;charset=utf-8");
            System.out.println(exception);
            // 有很多登录失败的异常
            HashMap<String, Object> map = new HashMap<>(4);
            map.put("code", 401);
            // instanceof 判断左右是否是右边的 一个实例  这里的exception已经是一个具体的错误了
            if (exception instanceof LockedException) {
                map.put("msg", "账户被锁定,登陆失败!");
            } else if (exception instanceof BadCredentialsException) {
                map.put("msg", "账户或者密码错误,登陆失败!");
            } else if (exception instanceof DisabledException) {
                map.put("msg", "账户被禁用,登陆失败!");
            } else if (exception instanceof AccountExpiredException) {
                map.put("msg", "账户已过期,登陆失败!");
            } else if (exception instanceof CredentialsExpiredException) {
                map.put("msg", "密码已过期,登陆失败!");
            } else {
                map.put("msg", "登陆失败!");
            }
            ObjectMapper objectMapper = new ObjectMapper();
            String s = objectMapper.writeValueAsString(map);
            PrintWriter writer = response.getWriter();
            writer.write(s);
            writer.flush();
            writer.close();
        };
    }

5.重新启动工程进行登录测试json返回

6.Spring Security 方法授权 权限访问限制

我们使用方法级别的授权后,只需要在 controller 对应的方法上添加注解即可了,不需要再 webSecurityConfig 中配置匹配的 url 和权限了,这样就爽多了

1.相关注解说明

@PreAuthorize 在方法调用前进行权限检查

@PostAuthorize 在方法调用后进行权限检查

@Secured 上面的三个注解如果要使用的话必须加上

@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)

如果只使用 PreAuthorize 就只用开启 prePostEnabled = true

如果只使用@Secured 就只用开启 securedEnabled = true 这种方式不推荐,有坑 坑在这里 @Secured,而@Secured 对应的角色必须要有 ROLE_前缀

2.在 WebSecurityConfig 类或者启动类上添加注解

image-20210409141018951-647cbce9e33748558f20a2c032eff955.png

3.注释掉 WebSecurityConfig 配置 url 和权限的代码

image-20210409141117632-ff4e1784f0024fe29899bddde84e5905.png

image-20210409141135380-cd8546bcbd5b438fae8e8a0ea23978d2.png

4.修改 controller,给方法添加注解

不加注解的,都可以访问,加了注解的,要有对应权限才可以访问哦

/**
     * 开启方法权限的注解
     *
     * @return
     */
    @GetMapping("add")
    @PreAuthorize("hasAuthority('sys:add')")
    public String add() {
        return "欢迎来到主ADD";
    }
    @GetMapping("update")
    @PreAuthorize("hasAuthority('sys:update')")
    public String update() {
        return "欢迎来到UPDATE";
    }
    @GetMapping("delete")
    @PreAuthorize("hasAuthority('sys:delete')")
    public String delete() {
        return "欢迎来到DELETE";
    }
    @GetMapping("select")
    @PreAuthorize("hasAuthority('sys:select')")
    public String select() {
        return "欢迎来到SELECT";
    }
    @GetMapping("role")
    public String role() {
        return "欢迎来到ROLE";
    }

5.重新启动即可。


相关文章
|
3天前
|
存储 JSON JavaScript
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)-1
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)
73 0
|
3天前
|
安全 数据安全/隐私保护
Springboot+Spring security +jwt认证+动态授权
Springboot+Spring security +jwt认证+动态授权
|
3天前
|
安全 Java 数据库
SpringSecurity+JWT前后端分离架构登录认证
在SpringSecurity实现前后端分离登录token认证详解_springsecurity前后端分离登录认证-CSDN博客基础上进行重构,实现前后端分离架构登录认证,基本思想相同,借鉴开源Gitee代码进行改造,具有更好的代码规范。
186 1
|
3天前
|
JSON 前端开发 安全
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)-2
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)
61 0
|
3天前
|
安全 Java API
深度解析 Spring Security:身份验证、授权、OAuth2 和 JWT 身份验证的完整指南
Spring Security 是一个用于保护基于 Java 的应用程序的框架。它是一个功能强大且高度可定制的身份验证和访问控制框架,可以轻松地集成到各种应用程序中,包括 Web 应用程序和 RESTful Web 服务。 Spring Security 提供了全面的安全解决方案,用于身份验证和授权,并且可以用于在 Web 和方法级别上保护应用程序。
325 0
|
3天前
|
JSON 安全 Java
Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token5
Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token5
|
3天前
|
NoSQL Java Redis
Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token4
Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token4
|
3天前
|
Java
Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token4
Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token4
|
3天前
|
Java
Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token3
Springboot最全权限集成Redis-前后端分离-springsecurity-jwt-Token3
|
3天前
|
存储 JSON 算法
SpringBoot之JWT令牌校验
SpringBoot之JWT令牌校验
23 2