前后端不分离架构 -- SpringSecurity用法

**说明:本节介绍得是 前后端不分离结构 SpringSecurity整个流程基本用法。**

一、界面流程介绍:

1、访问首页展示:

首页:不进行拦截

2、每个按钮的访问权限不尽相同,如:"验证zs"按钮表示 登录用户必须拥有USER的访问权限才可访问,"验证li"按钮表示 登录用户必须拥有READ的访问权限才可访问。点击按钮时,未登录,则跳转登陆界面。

登录拦截界面

3、登录成功后,再次访问,便可访问成功。

在这里插入图片描述

4、访问其他无权限的按钮时,报无权限。

在这里插入图片描述
用户不同的权限,访问不同的按钮

二、代码描述

1、数据库表结构:

  1. 用户表:
    sys_user
  2. 角色表:
    sys_role
  3. 用户角色关联表:
    sys_user_role
  4. 还可添加权限表、角色_权限表

2、service层(只写服务接口,具体实现不做介绍)

/**
 * @Author: ckw
 * @description: 用户服务
 * @date: 2022/2/10 16:07
 */
public interface SysUserService {

    /**
     * @Author: ckw
     * @description: 根据用户名查询用户
     * @date: 2022/2/10 16:07
     * @params: 用户名
     * @return 用户
     */
    SysUser getSysUserInfo(String username);

    /**
     * @Author: ckw
     * @description: 保存用户
     * @date: 2022/2/10 16:07
     * @params: 用户
     * @return 是否成功
     */
    Boolean saveSysUser(SysUser sysUser);

}


/**
 * @Author ckw
 * @Date 2022/2/8 19:54
 * @Version 1.0
 * @description: 角色服务
 */
public interface SysRoleService {

    /**
     * @Author: ckw
     * @description: 根据用户id查询用户所拥有角色
     * @date: 2022/2/10 16:09
     * @params: 用户id
     * @return 用户对应角色 
     */
    List<SysRole> findRoleByUser(Integer userid);

}

3、Controller:

/**
 * @Author: ckw
 * @description: 验证码控制器
 * @date: 2022/2/10 15:00
 */
@Controller
@RequestMapping("/captcha")
public class CaptchaController {

    //定义一个值,用来生成验证码的图片
    //图像宽度 120像素
    private int width = 120;

    //图像高度 30 像素
    private int height = 30;

    //图片内容在图片的起始位置 12像素
    private  int drawY = 20;

    //文字的间隔  18像素
    private int space = 15;

    //验证码有个文字
    private int charCount = 6;

    //验证码的内容数组
    private String chars []= {"A","B","C","D","E","F",
    "G","H","I","J","K","L","M","N","O","P","T","U","V","W",
    "X","Y","Z","1","2","3","4","5","6","7","8","9","0"};



    //定义方法:生成验证码内容。 在一个图片上,写入文字
    @GetMapping("/code")
    public void makeCaptchaCode(HttpServletRequest request,HttpServletResponse response) throws IOException {

        /*
           验证码:需要在内存中绘制一个图片BufferedImage.
           向这个图片中写入文字。 把绘制好内容的图片响应给请求
         */

        //创建一个背景透明的图片,使用rgb表示颜色的
        BufferedImage image = new BufferedImage(width,height, BufferedImage.TYPE_INT_RGB);

        //获取画笔
        Graphics g  = image.getGraphics();

        //设置使用画笔是白颜色
        g.setColor(Color.white);

        //给image画板都涂成白色的
        // fillRect(矩形的起始x,矩形的起始y, 矩形的宽度,矩形的高度)
        g.fillRect(0,0,width,height);

        //画内容
        //创建一个字体
        Font font  = new Font("宋体",Font.BOLD,16);
        g.setFont(font);

        g.setColor(Color.black);
        //在画布上,写一个文字
        //参数: 文字,x,y坐标
        //g.drawString("中",10,drawY);

        StringBuffer buffer = new StringBuffer("");
        int ran = 0;
        int len = chars.length;
        for(int i=0;i<charCount;i++){
            ran  = new Random().nextInt(len);
            buffer.append(chars[ran]);
            g.setColor(makeColor());
            g.drawString(chars[ran],(i+1)*space,drawY);
        }

        //绘制干扰线
        for(int m=0;m<4;m++){
            g.setColor(makeColor());
            int dot [] = makeLineDot();
            g.drawLine(dot[0],dot[1],dot[2],dot[3]);
        }

        //把生成的验证码存储到session中
        request.getSession().setAttribute("code",buffer.toString());

        //设置没有缓冲
        response.setHeader("Pragma","no-cache");
        response.setHeader("Cache-Control","no-cache");
        response.setDateHeader("Expires",0);
        response.setContentType("image/png");
        OutputStream out = response.getOutputStream();
        /*
           RenderedImage im, 输出的图像
           String formatName, 图像的格式 jpg,jpeg, png
           ImageOutputStream output 输出到哪
         */
        ImageIO.write(image,"png",out);
        out.flush();
        out.close();

    }

    private Color makeColor(){
        Random random = new Random();
        int r = random.nextInt(255);
        int g = random.nextInt(255);
        int b = random.nextInt(255);

        return new Color(r,g,b);
    }

    private int [] makeLineDot(){
        Random random = new Random();
        int x1 = random.nextInt(width/2);
        int y1 = random.nextInt(height);
        int x2 = random.nextInt(width);
        int y2 = random.nextInt(height);
        return new int[]{x1,y1,x2,y2};

    }
}

/**
 * @Author: ckw
 * @description: 首页
 * @date: 2022/2/10 15:00
 */
@Controller
public class IndexController {

    @GetMapping("/index")
    public String toIndexHtml(){
        return "forward:/index.html";
    }
}


/**
 * @Author: ckw
 * @description: 访问
 * @date: 2022/2/10 15:00
 */
@RestController
public class MyController {

    @GetMapping(value = "/access/user",produces = "text/html;charset=utf-8")
    public String sayUser(){
        return "zs 是 user 角色";
    }

    @GetMapping(value = "/access/read",produces = "text/html;charset=utf-8")
    public String sayRead(){
        return "lisi 是 read 角色";
    }

    @GetMapping(value = "/access/admin",produces = "text/html;charset=utf-8")
    public String sayAdmin(){
        return "admin 是 user , admin 角色";
    }
}

4、实现UserDetailsService 查询用户是否存在(在这里可以设置用户的权限)

/**
 * @Author ckw
 * @Date 2022/2/8 9:01
 * @Version 1.0
 * @description: 用户详情
 */
@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private SysRoleService sysRoleService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser sysUserInfo = sysUserService.getSysUserInfo(username);
        if(sysUserInfo != null){
            List<SysRole> sysRoles = sysRoleService.findRoleByUser(sysUserInfo.getId());
            List<GrantedAuthority> authorityList = new ArrayList<>();
            sysRoles.forEach(role ->{
                //角色必须以ROLE_开头
                GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" +role.getName());
                authorityList.add(authority);
            });
            sysUserInfo.setAuthorities(authorityList);
        }
        return sysUserInfo;
    }
}

5、Security 核心配置类WebSecurityConfigurerAdapter

/**
 * @Author ckw
 * @Date 2022/2/7 17:09
 * @Version 1.0
 * @description: security控制管理器
 */
@Configuration //配置类
@EnableWebSecurity //启动security安全框架功能
//@EnableGlobalMethodSecurity(prePostEnabled = true) //开启基于方法的安全认证机制
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {

    //把SuccssHandler , FailureHandler注入进来
    @Autowired
    private AuthenticationSuccessHandler successHandler;

    @Autowired
    private AuthenticationFailureHandler failureHandler;

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        System.out.println("=======configure HttpSecurity========== ");
        http.authorizeRequests()
                //指定那些地址可以直接访问, 和登录有关的需要进行指定
                .antMatchers("/index","/myajax.html","/login","/js/**","/captcha/**").permitAll()
                .antMatchers("/access/user/**").hasRole("USER")
                .antMatchers("/access/read/**").hasRole("READ")
                .antMatchers("/access/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .successHandler(successHandler)
                .failureHandler(failureHandler)
                .loginPage("/myajax.html")  //登录的自定义视图页面
                .loginProcessingUrl("/login") //form中登录的访问uri地址
                .and()
                //关于跨域访问的安全设置,先禁用
                .csrf().disable();

        //在框架的过滤器链条中,增加一个自定义过滤器
        http.addFilterBefore(new VerificationCodeFilter(), UsernamePasswordAuthenticationFilter.class);


    }
}

6、登录成功拦截器、登录失败拦截器

/**
 * @Author ckw
 * @Date 2022/2/8 9:01
 * @Version 1.0
 * @description: 登录成功拦截器
 */
@Component
public class MySuccessHandler implements AuthenticationSuccessHandler {
    /*
       参数:
         request : 请求对象
         response:应答对象
         authentication: spring security框架验证用户信息成功后的封装类。
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        //登录的用户信息验证成功后执行的方法
        response.setContentType("text/json;charset=utf-8");

        Result result = new Result();
        result.setCode(0);
        result.setError(1000);
        result.setMsg("登录成功");

        OutputStream out = response.getOutputStream();
        ObjectMapper om = new ObjectMapper();
        om.writeValue(out,result);
        out.flush();
        out.close();


    }
}


/**
 * @Author: ckw
 * @description: 登录失败拦截器
 * @date: 2022/2/10 15:04
 */
@Component
public class MyFailureHandler implements AuthenticationFailureHandler {

    private Result result;

    public Result getResult() {
        return result;
    }

    public void setResult(Result result) {
        this.result = result;
    }

    /*
           参数:
             request : 请求对象
             response:应答对象
             authentication: spring security框架验证用户信息成功后的封装类。
         */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response,
                                        AuthenticationException e) throws IOException {
        //当框架验证用户信息失败时执行的方法
        response.setContentType("text/json;charset=utf-8");

        if( result == null){
            Result  localResult  = new Result();
            localResult.setCode(1);
            localResult.setError(1001);
            localResult.setMsg("登录失败");
            result = localResult;
        }


        OutputStream out = response.getOutputStream();
        ObjectMapper om = new ObjectMapper();
        om.writeValue(out,result );
        out.flush();
        out.close();

    }
}

7、验证码拦截器(只拦截登录接口)

/**
 * @Author ckw
 * @Date 2022/2/8 9:01
 * @Version 1.0
 * @description: 验证码拦截器
 */
public class VerificationCodeFilter extends OncePerRequestFilter {

    private MyFailureHandler failureHandler = new MyFailureHandler();
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {

        System.out.println("VerificationCodeFilter  doFilterInternal ");
        //只有是login操作,才需要这个过滤器参与验证码的使用
        String uri = request.getRequestURI();
        if( !"/login".equals(uri)){
            //过滤器正常执行,不参与验证码操作
            filterChain.doFilter(request,response);
        } else {
            //登录操作,需要验证code
            try{
                //验证:code是否正确
                verifcatioinCode(request);
                //如果验证通过,过滤器正常执行
                filterChain.doFilter(request,response);

            }catch (VerificationException e){
                Result result  = new Result();
                result.setCode(1);
                result.setError(1002);
                result.setMsg("验证码错误!!!");
                failureHandler.setResult(result);
                failureHandler.onAuthenticationFailure(request,response,e);
            }
        }
    }

    private void verifcatioinCode(HttpServletRequest request){
        HttpSession session = request.getSession();
        //获取请求中的code
        String requestCode = request.getParameter("code");
        //获取session中的code
        String sessionCode = "";

        Object attr = session.getAttribute("code");
        if(attr !=null ){
            sessionCode = (String)attr;
        }
        System.out.println("VerificationCodeFilter  doFilterInternal requestCode:"+requestCode+"|sessionCode:"+sessionCode);
        //处理逻辑
        if(!StringUtils.isEmpty(sessionCode)){
            //在session中的code, 用户看到这个code了。
            //如果能到这段代码,说明用户已经发起了登录请求的。
            //session中的现在的这个code就应该无用
            session.removeAttribute("code");
        }

        //判断code是否正确。
        if( StringUtils.isEmpty(requestCode) ||
                StringUtils.isEmpty(sessionCode) ||
                !requestCode.equals(sessionCode) ){
            //失败
            throw new VerificationException();
        }
    }
}

8、响应类

/**
 * @Author: ckw
 * @description: 结果
 * @date: 2022/2/10 15:00
 */
public class Result {
    // code=0 成功; code =1 失败
    private int code;
    //表示错误码
    private int error;
    //消息文本
    private String msg;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public int getError() {
        return error;
    }

    public void setError(int error) {
        this.error = error;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

三、整体走向:

  1. MyWebSecurityConfig security控制管理器配置参数
  2. 用户登录时,先验证码拦截器拦截校验VerificationCodeFilter.doFilterInternal()
  3. 登录时,查询数据库UserDetailServiceImpl.loadUserByUsername()方法,对用户进行验证
  4. 验证成功:MySuccessHandler.onAuthenticationSuccess();验证失败:MyFailureHandler.onAuthenticationFailure()。

四、小点

  1. 可以设置登录无操作有效期:
    在这里插入图片描述
    在这里插入图片描述
  2. 可以自己实现token,进行校验
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值