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


1.1 基本介绍
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。
一般流程
登录 ->认证 -> 授权
- 当用户登录时,前端将用户输入的用户名、密码信息传输到后台,后台用一个类对象将其封装起来,通常使用的是
UsernamePasswordAuthenticationToken这个类。 - 程序负责验证这个类对象。验证方法是调用
Service根据username从数据库中取用户信息到实体类的实例中,比较两者的密码,如果密码正确就成功登陆,同时把包含着用户的用户名、密码、所具有的权限等信息的类对象放到SecurityContextHolder(安全上下文容器,类似Session)中去。 - 用户访问一个资源的时候,首先判断是否是受限资源。然后判断是否未登录,没有则跳到登录页面。
- 如果用户已经登录,访问一个受限资源的时候,程序要根据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"

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

被折叠的 条评论
为什么被折叠?



