第七章 项目进阶,构建安全高效的企业服务

本章详细介绍了如何使用Spring Security构建企业级服务的安全措施,包括Spring Security的基本概念、配置、授权流程,以及实战中如何进行权限控制、Redis高级数据类型的应用、网站数据统计、任务执行与调度。此外,还涵盖了热帖排行的实现、文件上传至服务器以及性能优化等内容。
摘要由CSDN通过智能技术生成

第七章 项目进阶,构建安全高效的企业服务

1、Spring Security

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

1.1 基本介绍

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。
一般流程

登录 ->认证 -> 授权

  1. 当用户登录时,前端将用户输入的用户名、密码信息传输到后台,后台用一个类对象将其封装起来,通常使用的是UsernamePasswordAuthenticationToken这个类。
  2. 程序负责验证这个类对象。验证方法是调用Service根据username从数据库中取用户信息到实体类的实例中,比较两者的密码,如果密码正确就成功登陆,同时把包含着用户的用户名、密码、所具有的权限等信息的类对象放到SecurityContextHolder(安全上下文容器,类似Session)中去。
  3. 用户访问一个资源的时候,首先判断是否是受限资源。然后判断是否未登录,没有则跳到登录页面。
  4. 如果用户已经登录,访问一个受限资源的时候,程序要根据url去数据库中取出该资源所对应的所有可以访问的角色,然后拿着当前用户的所有角色一一对比,判断用户是否可以访问(这里就是和权限相关)。

优点

  • Spring Security对Spring整合较好,使用起来更加方便;
  • 有更强大的Spring社区进行支持;
  • 支持第三方的 oauth 授权。

1.2 SpringSercurityDemo

1. 导包
<!--spring Security-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2. 实体类编写权限
2.1 实现UserDetails接口
public class User implements UserDetails {
   
}
2.2 实现方法并赋予权限
// 账号是否过期
@Override
public boolean isAccountNonExpired() {
   
    return true;
}

// 账号是否锁定
@Override
public boolean isAccountNonLocked() {
   
    return true;
}

// 凭证未过期
@Override
public boolean isCredentialsNonExpired() {
   
    return true;
}

// 账号是否可用
@Override
public boolean isEnabled() {
   
    return true;
}

// 获得权限
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
   
    List<GrantedAuthority> list=new ArrayList<>();
    list.add(new GrantedAuthority() {
   
        // 每一个封装一个权限
        @Override
        public String getAuthority() {
   
            switch (type){
   
                case 1:
                    return "ADMIN";
                default:
                    return "USER";
            }
        }
    });
    return list;
}
3. 业务层

实现UserDetailsService接口

public class UserService implements UserDetailsService {
   
}

重写方法:通过username加载用户

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
   
    return this.findUserByName(username);
}
4. SpringSercurity配置类
4.1 忽略静态资源
@Override
public void configure(WebSecurity web) throws Exception {
   
    // 忽略静态资源
    //super.configure(web);
    web.ignoring().antMatchers("/resource/**");
}

4.2 认证
// 认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
   
    //super.configure(auth);

    //内置的认证规则   加密
    auth.userDetailsService(userService).passwordEncoder(new Pbkdf2PasswordEncoder("12345"));

    //自定义认证规则
    auth.authenticationProvider(new AuthenticationProvider() {
   
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
   
            String username = authentication.getName();
            String password = (String) authentication.getCredentials(); // 凭证

            User user = userService.findUserByName(username);
            if (user==null){
   
                throw  new UsernameNotFoundException("账号不存在");
            }
            // md5加密生成密码
            password = CommunityUtil.md5(password + user.getSalt());
            // 判断密码是否与数据库一直
            if (!user.getPassword().equals(password)){
   
                throw new BadCredentialsException("密码不正确");
            }
            //principal: 主要信息, credentials:整数(密码), authorities:权限
            return new UsernamePasswordAuthenticationToken(user,user.getUsername(),user.getAuthorities());

        }

        // 当前的AuthenticationProvider支持那种类型的认证
        @Override
        public boolean supports(Class<?> aClass) {
   
            // UsernamePasswordAuthenticationToken:Authentication接口的常用实现类
            return UsernamePasswordAuthenticationToken.class.equals(aClass); //用户密码验证 还以扫脸等验证
        }
    });
}
4.3 授权
// 授权
@Override
protected void configure(HttpSecurity http) throws Exception {
   
    //super.configure(http);
    // 登录相关配置
    http.formLogin()
            .loginPage("/loginpage")
            .loginProcessingUrl("/login")
            // 登录成功时
            .successHandler(new AuthenticationSuccessHandler() {
   
                @Override
                public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
   
                    response.sendRedirect(request.getContextPath()+"/index");
                }
            })
            // 登录失败时
            .failureHandler(new AuthenticationFailureHandler() {
   
                @Override
                public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
   
                    request.setAttribute("error",e.getMessage());
                    request.getRequestDispatcher("/loginpage").forward(request,response);
                }
            });

    // 退出相关配置
    http.logout()
            .logoutUrl("/logout")
            .logoutSuccessHandler(new LogoutSuccessHandler() {
   
                @Override
                public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
   
                        response.sendRedirect(request.getContextPath()+"/index");
                }
            });

    //授权配置
    http.authorizeRequests()
            .antMatchers("/letter").hasAnyAuthority("USER","ADMIN")
            .antMatchers("/admin").hasAnyAuthority("ADMIN")
            .and().exceptionHandling().accessDeniedPage("/denied");
}

授权中处理验证码

// 增加filter,处理验证码
http.addFilterBefore(new Filter() {
   
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
   
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        if (request.getServletPath().equals("/login")){
   
            String verifyCode=request.getParameter("verifyCode");
            if (verifyCode==null||!verifyCode.equalsIgnoreCase("1234")){
   
                request.setAttribute("error","验证码错误!");
                request.getRequestDispatcher("/loginpage").forward(request,response);
                return;
            }
        }
        // 放行
        filterChain.doFilter(request,response);
    }
}, UsernamePasswordAuthenticationFilter.class);

授权中处理RememberMe

// 记住我
http.rememberMe()
        .tokenRepository(new InMemoryTokenRepositoryImpl())
        .tokenValiditySeconds(3600*24)
        .userDetailsService(userService);
5. 前端页面

配置拒绝访问页面

HomeController

// 拒绝访问的提示
@RequestMapping(value = "/denied",method = RequestMethod.GET)
public String getDenyPage(){
   
    return "/error/404";
}

将用户信息传入前端页面

@RequestMapping(path = "/index", method = RequestMethod.GET)
public String getIndexPage(Model model) {
   
    // 认证成功后,结果通过securityContextHolder存入SecurityContext中
    Object obj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    if (obj instanceof User){
   
         model.addAttribute("loginUser",obj);
    }

    return "/index";
}

index.html

在这里插入图片描述
login.html

在这里插入图片描述

2、权限控制

在这里插入图片描述

2.1 登录检查

废弃拦截器

在这里插入图片描述

2.2 授权配置

SecurityConfig

  • 忽略拦截静态资源
  • 授权
  • 修改退出路径
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter implements CommunityConstant {
   

    @Override
    public void configure(WebSecurity web) throws Exception {
   
        // 忽略拦截静态资源
        web.ignoring().antMatchers("/resources/**");
    }
    // 认证用自己的

    //授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
   
        http.authorizeRequests()
                .antMatchers(
                        "/user/setting",
                        "/user/upload",
                        "/discuss/add",
                        "/comment/add/**",
                        "/letter/**",
                        "/notice/**",
                        "/like",
                        "/follow",
                        "/unfollow"
                )
                .hasAnyAuthority(AUTHORITY_USER, AUTHORITY_ADMIN, AUTHORITY_MODERATOR)
                .anyRequest().permitAll() // 除了上述请求,其他都允许访问
                .and().csrf().disable(); // 关闭csrf检查
        // 权限不够时的处理
        http.exceptionHandling()
                .authenticationEntryPoint(new AuthenticationEntryPoint() {
   
                    // 没有登录时的处理
                    @Override
                    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
   
                        // 获取当前请求的请求头,判断同步还是异步
                        String xRequestedWith = request.getHeader("x-requested-with");
                        if ("XMLHttpRequest".equals(xRequestedWith)) {
   
                            // 异步
                            response.setContentType("application/plain;charset=utf-8");
                            PrintWriter writer = response.getWriter();
                            writer.write(CommunityUtil.getJsonString(403, "你还没有登录!"));
                        } else {
   
                            // 同步
                            response.sendRedirect(request.getContextPath() + "/login");
                        }
                    }
                })
                .accessDeniedHandler(new AccessDeniedHandler() {
   
                    // 权限不足时的处理
                    @Override
                    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
   
                        // 获取当前请求的请求头,判断同步还是异步
                        String xRequestedWith = request.getHeader("x-requested-with");
                        if ("XMLHttpRequest".equals(xRequestedWith)) {
   
                            // 异步
                            response.setContentType("application/plain;charset=utf-8");
                            PrintWriter writer = response.getWriter();
                            writer.write(CommunityUtil.getJsonString(403, "你没有访问此功能的权限!"));
                        } else {
   
                            // 同步
                            response.sendRedirect(request.getContextPath() + "/denied");
                        }
                    }
                });

        // Security默认拦截/logout的请求
        // 覆盖它默认的逻辑,才能执行我们自己的推出代码
        http.logout().logoutUrl("/securityLogout");

    }
}

denied页面 HomeController

    /**
     * 获取错误页面
     * @return
     */
    @RequestMapping(value = "/denied", method = RequestMethod.GET)
    public String getDeniedPage() {
   
        return "/error/404";
    }

UserService

  • 获取用户的权限
// 获取用户权限
    public Collection<? extends GrantedAuthority> getAuthorities(int userId) {
   
        User user = this.findUserById(userId);
        List<GrantedAuthority> list =new ArrayList<>();
        list.add(new GrantedAuthority() {
   
            @Override
            public String getAuthority() {
   
                switch (user.getType()){
   
                    case 1:
                        return AUTHORITY_ADMIN;
                    case 2:
                        return AUTHORITY_MODERATOR;
                    default:
                        return AUTHORITY_USER;
                }
            }
        });

        return list;
    }

2.3 认证方案

将认证的结果存入Security

loginTicketInterceptor
在这里插入图片描述

退出时清理数据
在这里插入图片描述
在这里插入图片描述

2.4 CSRF配置

在这里插入图片描述
攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。

SpringSecurity中,服务器给浏览器发送一个Tocken,
在这里插入图片描述
以发布帖子为例

index.html

<meta name="_csrf" th:content="${_csrf.token}">
<meta name="_csrf_header" th:content="${_csrf.headerName}">

index.js

// 发送ajax请求之前,将CSRF令牌设置到请求的消息头中
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function (e, xhr, options) {
   
    xhr.setRequestHeader(header, token);
});

关闭防止CSRF攻击

Security配置类Configure(HttpSecurity http)

.and().csrf().disable(); // 关闭csrf检查

3、置顶、加精、删除

在这里插入图片描述

3.1 功能实现

在dao接口中添加两个方法

DiscussPostMapper

// 修改类型
int updateType(int id,int type);

// 修改状态
int updateStatus(int id,int status);

DiscussPostMapper.xml

    <update id="updateType">
        update discuss_post
        set type = #{type}
        where id = #{id}
    </update>

    <update id="updateStatus"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值