- 前言
- 第一部分 基础应用开发
- 第 1 章 Spring Boot 入门
- 第 2 章 在 Spring Boot 中使用数据库
- 第 3 章 Spring Boot 界面设计
- 第 4 章 提高数据库访问性能
- 第 5 章 Spring Boot 安全设计
- 第二部分 分布式应用开发
- 第 6 章 Spring Boot SSO
- 第 7 章 使用分布式文件系统
- 第 8 章 云应用开发
- 第 9 章 构建高性能的服务平台
- 第三部分 核心技术源代码分析
- 第 10 章 Spring Boot 自动配置实现原理
- 第 11 章 Spring Boot 数据访问实现原理
- 第 12 章 微服务核心技术实现原理
- 附录 A 安装 Neo4j
- 附录 B 安装 MongoDB
- 附录 C 安装 Redis
- 附录 D 安装 RabbitMQ
- 结束语
5.2 安全策略配置
关于系统的安全管理及各种设计,Spring Security 已经大体上都实现了,只需要进行一些配置和引用,就能够正常使用。如代码清单 5-2 所示,安全配置类 SecurityConfiguration 继承了 Spring Security 的 WebSecurityConfigurerAdapter。这里可以使用 HttpSecurity 的一些安全策略进行配置,各项配置的解释如下:
- loginPage:设置一个使用自定义的登录页面 URL。
- loginSuccessHandler:设置自定义的一个登录成功处理器。
- permitAll:是完全允许访问的一些 URL 配置,并可以使用通配符来设置,这里将一些资源目录赋予可以完全访问的权限,由 settings 指定的权限列表也赋予了完全访问的权限。
- logout:设置使用默认的登出。
- logoutSuccessUrl:设定登出成功的链接。
- rememberMe:用来记住用户的登录状态,即用户没有执行退出时,再次打开页面将不用登录。
- csrf:即跨站请求伪造(cross-site request forgery),这是一个防止跨站请求伪造攻击的策略设置。
- accessDeniedPage:配置一个拒绝访问的提示链接。
其中,settings 是引用了自定义的配置参数。
代码清单 5-2 安全策略配置
public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private SecuritySettings settings; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin().loginPage("/login").permitAll().successHandler(login SuccessHandler()) .and().authorizeRequests() .antMatchers("/images/**", "/checkcode", "/scripts/**", "/styles/**").permitAll() .antMatchers(settings.getPermitall().split(",")).permitAll() .anyRequest().authenticated() .and().csrf().requireCsrfProtectionMatcher(csrfSecurityRequestMatcher()) .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER) .and().logout().logoutSuccessUrl(settings.getLogoutsuccssurl()) .and().exceptionHandling().accessDeniedPage(settings.getDenied page()) .and().rememberMe().tokenValiditySeconds(1209600).tokenRepository(tokenRepository()); } ...... }
5.2.1 权限管理规则
代码清单 5-2 中引用的 SecuritySettings 是自定义的一个配置类,如代码清单 5-3 所示。其中使用注解 @ConfigurationProperties 设定配置参数的前缀部分为 securityconfig,定义的各个配置参数的意义如下:
- logoutsuccessurl:用来定义退出成功的链接。
- permitall:用来定义允许访问的 URL 列表。
- deniedpage:用来设定拒绝访问的信息提示链接。
- urlroles:这是一个权限管理规则,是链接地址与角色权限的配置列表。
代码清单 5-3 自定义配置类
@ConfigurationProperties(prefix="securityconfig") public class SecuritySettings { private String logoutsuccessurl = "/logout"; private String permitall = "/api"; private String deniedpage = "/deny"; private String urlroles; ...... }
使用自定义配置参数后,可以在工程的配置文件 application.yml 中对安全管理进行集中配置,如代码清单 5-4 所示。
代码清单 5-4 使用自定义的 securityconfig 配置
securityconfig: logoutsuccssurl: / permitall: /rest/**,/bbs** deniedpage: /deny urlroles: /**/new = manage,admin; /**/edit/** = admin; /**/delete/** = admin
其中 urlroles 配置一个权限配置列表,这是我们设计的一种权限管理规则,列表中的每一个配置项用分号分隔,每一个配置项的等号左边是一个可以带上通配符的链接地址,等号右边是一个角色列表,角色之间用逗号分隔。每一个配置项表示包含等号左边字符串的链接地址,能够被等号右边的角色访问。
这将要求我们的控制器设计链接地址时,必须遵循这一权限管理规则,这样只要使用一个简单的配置列表,就能够覆盖整个系统的权限管理策略。设计控制器链接地址的规则如下,它包含了系统增删查改的所有操作。
- /**/new:新建;
- /**/edit/**:修改;
- /**/delete/**:删除;
- /**/show/**:查看;
- /**/list:列表查询。
使用这种规则之后,再来看看代码清单 5-4 中 urlroles 的权限配置,这里只需要简单的三个配置项,就已经完成了对一个应用系统所有权限的管理配置了。其中,新建操作只有 manage、admin 两个角色有权限,修改操作和删除操作只有 admin 这个角色有权限,至于没有在权限管理列表中配置的查看操作,因为没有限定角色访问,所以它能被所有用户访问。
这种权限策略配置好了之后,要让应用系统中的一个用户具有哪些权限,只要分配给这个用户一些角色就可以。
5.2.2 登录成功处理器
登录成功后,如果需要对用户的行为进行记录或者执行其他操作,可以使用登录成功处理器。代码清单 5-5 是一个登录成功处理器的定义,这里只是简单地输出了用户登录的日志。
代码清单 5-5 登录成功处理器
@Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException,ServletException { User userDetails = (User)authentication.getPrincipal(); log.info("登录用户 user:" + userDetails.getName() + "login"+request.getContextPath()); log.info("IP:" + getIpAddress(request)); super.onAuthenticationSuccess(request, response, authentication); }
5.2.3 防攻击策略
因为 Spring Security 的跨站请求伪造(cross-site request forgery,CSRF)即阻止跨站请求伪造攻击的功能很完善,所以使用 Spring Security 之后,对于新建、修改和删除等操作,必须进行特殊的处理,才能正常使用。这要求在所有具有上面操作请求的页面上提供如下代码片段,因为我们的页面设计使用了 Thymeleaf 模板,所以只要在 layout.hmtl 的页头上加入下面两行代码即可,loyou.html 是所有页面都会用到的一个页面文件。
<meta name="_csrf" th:content="${_csrf.token}"/> <meta name="_csrf_header" th:content="${_csrf.headerName}"/>
还要在 loyout.html 中引用脚本文件 public.js,然后在 public.js 中增加一个函数,如代码清单 5-6 所示。这样做的意思是,在表单提交时放入一个 token,服务端验证该 token 是否有效,只允许有效的 token 请求,否则拒绝当前操作。这样就能够很好地起到防御 CSRF 攻击的目的。
代码清单 5-6 阻止 CSRF 攻击策略
$(function () { 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); }); });
如果要对第三方开放接口,上面的方法就不适用了,这时只能对特定的 URL 使用排除 CSRF 保护的方法来实现。代码清单 5-7 对指定的 URL 排除对其进行 CSRF 的保护。
代码清单 5-7 排除 CSRF 保护策略
public class CsrfSecurityRequestMatcher implements RequestMatcher { protected Log log = LogFactory.getLog(getClass()); private Pattern allowedMethods = Pattern .compile("^(GET|HEAD|TRACE|OPTIONS)$"); /** * 需要排除的 url 列表 */ private List<String> execludeUrls; @Override public boolean matches(HttpServletRequest request) { if (execludeUrls != null && execludeUrls.size() > 0) { String servletPath = request.getServletPath(); for (String url : execludeUrls) { if (servletPath.contains(url)) { log.info("++++"+servletPath); return false; } } } return !allowedMethods.matcher(request.getMethod()).matches(); } …… }
然后在配置类中,加入需要排除阻止 CSRF 攻击的链接列表,如代码清单 5-8 所示,只要链接地址中包含“/rest”字符串,就将对其忽略 CSRF 保护策略。
代码清单 5-8 在安全配置类加入需要排除 CSRF 保护的列表
private CsrfSecurityRequestMatcher csrfSecurityRequestMatcher(){ CsrfSecurityRequestMatcher csrfSecurityRequestMatcher = new CsrfSecurity RequestMatcher(); List<String> list = new ArrayList<String>(); list.add("/rest/"); csrfSecurityRequestMatcher.setExecludeUrls(list); return csrfSecurityRequestMatcher; }
5.2.4 记住登录状态
代码清单 5-2 中的安全策略配置中有一行配置:rememberMe().tokenValiditySeconds(86400).tokenRepository(tokenRepository()),它是用来记住用户登录状态的一个配置,其中 86400 指定记住的时间秒数,即为 1 天时间。为了实现这个功能,需要将一个用户的登录令牌等信息保存在数据库中,这需要在配置类中指定连接数据库的数据源,如代码清单 5-9 所示。
代码清单 5-9 指定保存登录用户 token 的数据源
@Autowired @Qualifier("dataSource") private DataSource dataSource; @Bean public JdbcTokenRepositoryImpl tokenRepository(){ JdbcTokenRepositoryImpl jtr = new JdbcTokenRepositoryImpl(); jtr.setDataSource(dataSource); return jtr; }
同时,还应该在数据库中增加一个数据表 persistent_logins,这个表结构的定义是由 Spring Security 提供的,使用一个实体来实现,这样做的目的只是为了在系统启动时能够创建这个表结构而已,如代码清单 5-10 所示,它用来保存用户名、令牌和最后登录时间等信息。
代码清单 5-10 记住用户登录状态的实体建模
@Entity @Table(name = "persistent_logins") public class PersistentLogins implements java.io.Serializable{ @Id @Column(name = "series", length = 64, nullable = false) private String series; @Column(name = "username", length = 64, nullable = false) private String username; @Column(name = "token", length = 64, nullable = false) private String token; @Temporal(TemporalType.TIMESTAMP) @Column(name = "last_used", nullable = false) private Date last_used;…… }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论