SpringSecurity实现登录和权限【真~前后端分离】

整合这个SpringSecurity花了我好几天的时间,也让我很头疼。
倒不是因为它很难,只是我搜索到的前后端分离验证,多多少少都有些问题。
下面我就把我完整的代码贡献出来、避免后面的人也走坑。

1、阐述几个问题

这里有几个问题需要表达一下,当然你也可以直接跳到第二步开始。

1-1、什么是SpringSecurity

     它本质就是一个过滤器,然后在请求之前先执行这个过滤器,在这个过滤器里面我们去进行用户登录,和权限进行判断。

1-2、为什么我没有用JWT来生成token

     我觉得直接使用UUID生成Token就好了,没必要使用JWT来生成一个又长又麻烦的Token。

1-3、下面的代码是完整的代码嘛?

     可以说是了,本质上是基于Redis存储数据验证的,但是我觉得第一次学习Security的时候如果把全部都写好加上去显得很麻烦。Redis这块可以根据自己定义去实现,我都标识出来了,很简单。

1-4、如果我想获取全部的代码呢?

     这个也不用担心,我正在做这个前后端分离的,登录权限的框架,代码是开源的。今天先分享怎么去解决基于Security前后端分离,后面再写一篇文章基于整个登录权限的设计。

https://github.com/xdxTao/xdx-framework-SpringCloud

https://github.com/xdxTao/xdx-framework-vue

1-5、xxxxxx

      感觉真是坑,很多关于Security的视频,但是都没有讲到怎么解决前后端分离的问题。昨天晚上搞到凌晨1点才弄完。


2、代码部分

ps:token本应该随机生成的,但是这里只是演示,我就在yml里面写死了。其实这个很好理解的。

2-1、pom依赖
 <!-- fastjson -->
<dependency>
       <groupId>com.alibaba</groupId>
       <artifactId>fastjson</artifactId>
       <version>1.2.48</version>
</dependency>
<!-- Spring Security -->
<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-security</artifactId>
       <version>2.0.0.RELEASE</version>
</dependency>
2-2、AjaxResult

ps:这个就是一个结果集统一封装,相信大部分人都明白。

/**
 * 封装返回结果集
 *
 * @author 小道仙
 * @date 2020年2月17日
 */
@Data
@Accessors(chain = true)
public class AjaxResult<T> {
    /**
     * 返回状态码
     */
    private Integer code;

    /**
     * 返回的数据
     */
    private T data;

    /**
     * 总条数
     */
    private Integer total;

    /**
     * 成功与否
     */
    private Boolean success;

    /**
     * 消息提示
     */
    private String msg;

    /**
     * 错误描述
     */
    private String errDesc;

    /**
     * 用户token
     */
    private String xdxToken;

    public AjaxResult() {
    }

    /**
     * 操作失败
     * @param errDesc 错误信息
     *
     * @author 小道仙
     * @date 2020年2月17日
     */
    public static AjaxResult<?> failure(String errDesc) {
        return new AjaxResult<>().setErrDesc(errDesc).setSuccess(false);
    }

    /**
     * 操作成功
     * @param msg  返回消息
     * @param total 总条数
     * @param data 返回的数据
     *
     * @author 小道仙
     * @date 2020年2月17日
     */
    public static <T> AjaxResult<T> success(String msg,Integer total,T data){
        AjaxResult<T> result = new AjaxResult<>();
        result.setSuccess(true)
                .setTotal(total)
                .setMsg(msg);
        return result;
    }

    /**
     * 操作成功
     * @param total 总条数
     * @param data 返回的数据
     *
     * @author 小道仙
     * @date 2020年2月17日
     */
    public static <T> AjaxResult<T> success(T data,Integer total){
        AjaxResult<T> result = new AjaxResult<>();
        result.setSuccess(true)
                .setTotal(total)
                .setMsg("操作成功")
                .setData(data);
        return result;
    }

    /**
     * 操作成功
     * @param data 返回的数据
     *
     * @author 小道仙
     * @date 2020年2月22日
     */
    public static <T> AjaxResult<T> success(T data){
        AjaxResult<T> result = new AjaxResult<>();
        result.setSuccess(true)
                .setMsg("操作成功")
                .setData(data);
        return result;
    }

    /**
     * 操作成功
     * @param msg  返回消息
     *
     * @author 小道仙
     * @date 2020年2月17日
     */
    public static <T> AjaxResult<T> success(String msg){
        return success(msg,0,null);
    }

    /**
     * 操作成功
     * @param msg  返回消息
     * @param total 总条数
     *
     * @author 小道仙
     * @date 2020年2月17日
     */
    public static <T> AjaxResult<T> success(String msg,Integer total){
        return success(msg,total,null);
    }

    /**
     * 操作成功
     *
     * @author 小道仙
     * @date 2020年2月17日
     */
    public static <T> AjaxResult<T> success(){
        return success("操作成功",0,null);
    }
}

2-3、SecurityConfig

在这里插入图片描述

2-3-1:SecurityConfig
import com.xdx97.framework.config.security.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;


@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter  {

    /**
     *  未登陆时返回 JSON 格式的数据给前端(否则为 html)
     */
    @Autowired
    AjaxAuthenticationEntryPoint authenticationEntryPoint;

    /**
     * 注销成功返回的 JSON 格式数据给前端(否则为 登录时的 html)
     */
    @Autowired
    AjaxLogoutSuccessHandler logoutSuccessHandler;


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

        // 去掉 CSRF
        http.csrf().disable()
                .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
                .and()
                // 基于Token 不需要Session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                // 登录处理
                .and()
                .formLogin()
                .loginProcessingUrl("/user/login")
                .permitAll()

                //  登录和权限控制
                .and()
                .authorizeRequests()
                .anyRequest()
                // RBAC 动态 url 认证
                .access("@rbacauthorityservice.hasPermission(request,authentication)")



                //注销处理
                .and()
                .logout()//默认注销行为为logout
                .logoutUrl("/user/loginOut")
                .logoutSuccessHandler(logoutSuccessHandler)
                .permitAll();
    }
}

2-3-2:AjaxAuthenticationEntryPoint
import com.alibaba.fastjson.JSON;
import com.xdx97.framework.common.AjaxResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 用户没有登录时返回给前端的数据
 */
@Component
public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {

        AjaxResult ajaxResult = new AjaxResult();

        String flagName = httpServletRequest.getAttribute("flagName").toString();

        if (flagName.equals("未登录")){
            ajaxResult.setCode(888)
                    .setErrDesc("未登录,请登录!");
        } else if (flagName.equals("权限不足")){
            ajaxResult.setCode(999)
                    .setErrDesc("权限不足!");
        } else{
            ajaxResult.setCode(000)
                    .setErrDesc("系统异常!");
        };

        httpServletResponse.setContentType("text/html;charset=utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(ajaxResult));
    }
}

2-3-2:AjaxLogoutSuccessHandler
import com.alibaba.fastjson.JSON;
import com.xdx97.framework.common.AjaxResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 退出登录
 */
@Component
public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {

        // 从这里拿到token 然后把这个token注销
        String token = httpServletRequest.getHeader("xdxToken");

        // 去redis删除token

        System.out.println("123123213");


        AjaxResult ajaxResult = new AjaxResult();
        ajaxResult.setCode(100)
                .setErrDesc("退出成功!");

        httpServletResponse.setContentType("text/html;charset=utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(ajaxResult));
    }
}

2-3-2:RbacAuthorityService
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;

@Component("rbacauthorityservice")
public class RbacAuthorityService {

    @Autowired
    private Environment env;

    public boolean hasPermission(HttpServletRequest request,Authentication authentication) {

        // 获取当前请求的URI
        String requestURI = request.getRequestURI();

        // 放开登录url
        if (requestURI.equals("/user/login")){
            return true;
        }

        // 登录判断
        String token = request.getHeader("xdxToken");
        if (token == null || !token.equals(env.getProperty("xdxToken"))){
            request.setAttribute("flagName","未登录");
            return false;
        }

        // 权限判断
        // 利用token去Redis取出当前角色的权限,这里就直接写死了
        List<String> roles = new ArrayList<>();
        roles.add("/user/list");
        roles.add("/user/menu");
        roles.add("/user/loginOut");

        if (!roles.contains(requestURI)){
            request.setAttribute("flagName","权限不足");
            return false;
        }
        return true;
    }
}

2-4:另外我们需要三个接口,一个登录(/user/login),一个测试(/user/list),一个测试(/authority/menu/list)

ps:两个测试接口你不必在意里面的实现,直接打印一句话就也行了,主要是看能不能访问到。

2-4-1:登录(/user/login)

controller

    @GetMapping("/user/login")
    public AjaxResult<?> login(@RequestParam String userName, @RequestParam String userPassword){
        User user = new User();
        user.setUserName(userName).setUserPassword(userPassword);
        return userServiceImpl.login(user);
    }

service:这个Environment 是用来获取yml文件里面的值的

    @Autowired
    private Environment env;

    @Override
    public AjaxResult<?> login(User user) {
        AjaxResult ajaxResult = new AjaxResult();
        /**
         * 1、获取到了用户名和密码去进行判断是否正确
         * 2、如果验证不成功,这里我默认用户名密码必须等于 admin admin
         */
        if (! ("admin".equals(user.getUserName()) && "admin".equals(user.getUserPassword()))){
            ajaxResult.setCode(222).setErrDesc("用户名或密码错误!");
            return ajaxResult;
        }

        // 3、如果验证成功了,就返回 token,当然了我们现需要把token存入Redis这里就省略了
        ajaxResult.setCode(200).setMsg("登录成功!").setXdxToken(env.getProperty("xdxToken"));

        return ajaxResult;
    }
2-5、yml

在这里插入图片描述


2-6:总结

    RbacAuthorityService是对所有请求进行拦截的,当被拦截到时,就会进入AjaxAuthenticationEntryPoint

    当我们要退出的时候访问 (/user/loginOut) 这时会进入 AjaxLogoutSuccessHandler

    当然了你可以根据自己的需求继续加入Filter


3、演示

3-1:登录/退出

ps:我也不知道为啥登录只能用get请求,还没研究为什么,有兴趣可以自行研究。

在这里插入图片描述
在这里插入图片描述


3-2:测试其它的接口

在这里插入图片描述
在这里插入图片描述



如果对你有帮助,或者对我感觉兴趣的话,可以关注我的公众号支持一下我噢
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值