Spring Security用户认证和权限控制(默认实现)

1 背景

实际应用系统中,为了安全起见,一般都必备用户认证(登录)和权限控制的功能,以识别用户是否合法,以及根据权限来控制用户是否能够执行某项操作。

Spring Security是一个安全相关的框架,能够与Spring项目无缝整合,本文主要是介绍Spring Security默认的用户认证和权限控制的使用方法和原理,但不涉及到自定义实现。

Spring Security用户认证和权限控制(自定义实现)这篇文章专门讲解用户认证和权限控制相关的自定义实现。

2 实战示例

2.1 创建工程

创建一个名为authentication-server的spring boot工程,项目结构如下图所示:
在这里插入图片描述
说明:该spring boot工程主要是整合了Spring Security框架和Spring MVC框架。

2.2 配置说明

pom.xml配置文件如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.authentication.server</groupId><artifactId>authentication-server</artifactId><version>0.0.1-SNAPSHOT</version><name>authentication-server</name><description>统一用户认证中心</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.3.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

2.2 用户认证

要想使用Spring Security框架,配置类需要继承WebSecurityConfigurerAdapter类,并通过注解@EnableWebSecurity来启用Spring Security。

本文的用户认证是使用Spring Security默认的基于用户名和密码的表单认证,需要在配置类中重写protected void configure(AuthenticationManagerBuilder auth)方法,并在重写的方法中指定默认从哪里获取认证用户的信息,即指定一个UserDetailsService接口的实现类。此外,还需要重写protected void configure(HttpSecurity http)方法,并在重写的方法中进行一系列的安全配置。本示例的配置类WebSecurityConfig代码如下所示:

package com.authentication.server.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;/*** Spring Security配置类*/
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsServiceImpl;/*** 用户认证配置*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {/*** 指定用户认证时,默认从哪里获取认证用户信息*/auth.userDetailsService(userDetailsServiceImpl);}/*** Http安全配置*/@Overrideprotected void configure(HttpSecurity http) throws Exception {/*** 表单登录:使用默认的表单登录页面和登录端点/login进行登录* 退出登录:使用默认的退出登录端点/logout退出登录* 记住我:使用默认的“记住我”功能,把记住用户已登录的Token保存在内存里,记住30分钟* 权限:除了/toHome和/toUser之外的其它请求都要求用户已登录* 注意:Controller中也对URL配置了权限,如果WebSecurityConfig中和Controller中都对某文化URL配置了权限,则取较小的权限*/http.formLogin().defaultSuccessUrl("/toHome", false).permitAll().and().logout().permitAll().and().rememberMe().tokenValiditySeconds(1800).and().authorizeRequests().antMatchers("/toHome", "/toUser").permitAll().anyRequest().authenticated();}/*** 密码加密器*/@Beanpublic PasswordEncoder passwordEncoder() {/*** BCryptPasswordEncoder:相同的密码明文每次生成的密文都不同,安全性更高*/return new BCryptPasswordEncoder();}}

Spring Security进行用户认证时,需要根据用户的账号、密码、权限等信息进行认证,因此,需要根据查询到的用户信息封装成一个认证用户对象并交给Spring Security进行认证。查询用户信息并封装成认证用户对象的过程是在UserDetailsService接口的实现类(需要用户自己实现)中完成的。本示例的UserDetailsService接口实现类UserDetailsServiceImpl的代码如下所示:

package com.authentication.server.service.impl;import com.authentication.server.model.AuthUser;
import com.authentication.server.service.AuthUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;import java.util.ArrayList;
import java.util.List;/*** 自定义的认证用户获取服务类*/
@Component("userDetailsServiceImpl")
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate AuthUserService authUserServiceImpl;/*** 根据用户名获取认证用户信息*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {if(StringUtils.isEmpty(username)) {throw new UsernameNotFoundException("UserDetailsService没有接收到用户账号");} else {/*** 根据用户名查找用户信息*/AuthUser authUser = authUserServiceImpl.getAuthUserByUsername(username);if(authUser == null) {throw new UsernameNotFoundException(String.format("用户'%s'不存在", username));}List<GrantedAuthority> grantedAuthorities = new ArrayList<>();for (String role : authUser.getRoles()) {//封装用户信息和角色信息到SecurityContextHolder全局缓存中grantedAuthorities.add(new SimpleGrantedAuthority(role));}/*** 创建一个用于认证的用户对象并返回,包括:用户名,密码,角色*/return new User(authUser.getUsername(), authUser.getPassword(), grantedAuthorities);}}
}

查询用户信息的接口AuthUserService 的代码如下所示:

package com.authentication.server.service;import com.authentication.server.model.AuthUser;/*** 用户服务类*/
public interface AuthUserService {/*** 通过用户账号获取认证用户信息*/AuthUser getAuthUserByUsername(String username);}

查询用户信息的接口实现类AuthUserServiceImpl的代码如下所示:

package com.authentication.server.service.impl;import com.authentication.server.model.AuthUser;
import com.authentication.server.service.AuthUserService;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;/*** 用户服务实现类*/
@Service
public class AuthUserServiceImpl implements AuthUserService {/*** 通过用户账号获取用户信息*/@Overridepublic AuthUser getAuthUserByUsername(String username) {/*** 实际上这里应该是从数据库中查询或者是调用其它服务接口获取,* 为了方便,这里直接创建用户信息* admin用户拥有 ROLE_ADMIN 和 ROLE_EMPLOYEE 这两个角色* employee用户拥有 ROLE_EMPLOYEE 这个角色* temp用户没有角色*/if(username.equals("admin")) {AuthUser user = new AuthUser();user.setId(1L);user.setUsername("admin");/*** 密码为123(通过BCryptPasswordEncoderl加密后的密文)*/user.setPassword("$2a$10$U6g06YmMfRJXcNfLP28TR.xy21u1A5kIeY/OZMKBDVMbn7PGJiaZS");List<String> roles = new ArrayList<>();roles.add("ROLE_ADMIN");roles.add("ROLE_EMPLOYEE");user.setRoles(roles);return user;} else if(username.equals("employee")) {AuthUser user = new AuthUser();user.setId(2L);user.setUsername("employee");/*** 密码为123(通过BCryptPasswordEncoderl加密后的密文)*/user.setPassword("$2a$10$U6g06YmMfRJXcNfLP28TR.xy21u1A5kIeY/OZMKBDVMbn7PGJiaZS");List<String> roles = new ArrayList<>();roles.add("ROLE_EMPLOYEE");user.setRoles(roles);return user;} else if (username.equals("temp")) {AuthUser user = new AuthUser();user.setId(3L);user.setUsername("temp");/*** 密码为123(通过BCryptPasswordEncoderl加密后的密文)*/user.setPassword("$2a$10$U6g06YmMfRJXcNfLP28TR.xy21u1A5kIeY/OZMKBDVMbn7PGJiaZS");List<String> roles = new ArrayList<>();user.setRoles(roles);return user;} else {return null;}}}

用户信息实体类如下所示:

package com.authentication.server.model;import java.util.List;/*** 用户实体类*/
public class AuthUser {/** 用户ID */private Long id;/** 用户账号 */private String username;/** 账号密码 */private String password;/** 角色集合 */private List<String> roles;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public List<String> getRoles() {return roles;}public void setRoles(List<String> roles) {this.roles = roles;}
}

用户认证成功之后,可以通过@AuthenticationPrincipal注解来获取认证用户信息,本示例中获取认证用户信息的web入口类UserController的代码如下所示:

package com.authentication.server.controller;import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.security.Principal;/*** 用户接口类(返回JSON)*/
@RestController
public class UserController {/*** 获取登录后的Principal(需要登录)*/@GetMapping("/getPrincipal")public Object getPrincipal(@AuthenticationPrincipal Principal principal){return principal;}/*** 获取登录后的UserDetails(需要登录)*/@GetMapping("/getUserDetails")public Object getUserDetails(@AuthenticationPrincipal UserDetails userDetails) {return userDetails;}}

2.3 权限控制

Spring Security提供了默认的权限控制功能,需要预先分配给用户特定的权限,并指定各项操作执行所要求的权限。用户请求执行某项操作时,Spring Security会先检查用户所拥有的权限是否符合执行该项操作所要求的权限,如果符合,才允许执行该项操作,否则拒绝执行该项操作。

本示例中使用的是Spring Security提供的方法级别的权限控制,即根据权限来控制用户是否能够请求某个方法。首先,需要在工程的主启动类中使用注解@EnableGlobalMethodSecurity(prePostEnabled = true)来启动方法级别的权限控制,并指定是在方法执行之前进行权限验证;然后,需要在方法的入口处通过注解@PreAuthorize()来指定执行对应方法需要什么样的权限。

本示例的主启动类AuthenticationServerApplication的代码如下所示:

package com.authentication.server;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;/*** 主启动类*/
@ComponentScan("com.authentication.server.*")
@EnableGlobalMethodSecurity(prePostEnabled = true)
@SpringBootApplication
public class AuthenticationServerApplication {public static void main(String[] args) {SpringApplication.run(AuthenticationServerApplication.class, args);}}

本示例中使用的是基于角色的权限控制,即验证用户所拥有的角色是否符合执行某个方法所需要的角色,如果符合,才允许执行该方法,否则拒绝执行该方法。需要在方法入口处通过注解 @PreAuthorize(“hasRole(‘角色名称’)”)来指定执行对应方法需要什么角色,并且是在执行对应方法之前进行角色验证。

本示例的方法入口控制类PageController的代码如下所示:

package com.authentication.server.controller;import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;/*** 页面接口类(页面跳转)*/
@Controller
public class PageController {/*** 跳转到admin.html页面(需要登录,且需要ROLE_ADMIN角色)*/@GetMapping("/toAdmin")@PreAuthorize("hasRole('ROLE_ADMIN')")public String toAdmin() {return "admin.html";}/*** 跳转到employee.html页面(需要登录,且需要ROLE_EMPLOYEE角色)*/@GetMapping("/toEmployee")@PreAuthorize("hasRole('ROLE_EMPLOYEE')")public String toEmployee() {return "employee.html";}/*** 跳转到employee.html页面(需要登录,但不需要角色)* 注意:虽然WebSecurityConfig中配置了/toUser不需要登录,但是这里配置的权限更小,因此,/toUser以这里的配置为准*/@GetMapping("/toUser")@PreAuthorize("isAuthenticated()")public String toUser() {return "user.html";}/*** 跳转到home.html页面(需要登录,但不需要角色)* 注意:虽然这里配置了/toAbout不需要登录,但WebSecurityConfig中配置的权限更小,因此,/toAbout以WebSecurityConfig中配置的为准*/@RequestMapping("/toAbout")@PreAuthorize("permitAll")public String toAbout() {return "about.html";}/*** 跳转到home.html页面(不需要登录)*/@RequestMapping("/toHome")public String toHome() {return "home.html";}}

静态页面admin.html的代码如下所示:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>admin页面</title>
</head>
<body><h1>这是Admin页面(需要登录,且需要ROLE_ADMIN角色)</h1>
</body>
</html>

静态页面employee.html、user.html、home.html、about.html的代码与admin.html的相似,就不一一展示。

3 功能测试

3.1 用户认证功能测试

运行AuthenticationServerApplication主启动类以启动项目,然后通过浏览器访问以下地址请求toUser()方法(即访问user.html页面),由于toUser()方法需要用户经过认证之后才能访问,因此,会自动跳转到用户认证页面(如下图所示)进行认证:

http://localhost:8080/toUser

在这里插入图片描述
输入用户名admin和密码123并点击Sign in按钮,认证成功后会自动请求/toHome路径并跳转到自定义的认证成功跳转页面home.hmtl(如下图所示):
在这里插入图片描述
通过浏览器访问以下地址可以查看到认证用户信息如下图所示:

http://localhost:8080/getPrincipal
http://localhost:8080/getUserDetails

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

3.2 “记住我” 功能测试

用户进行认证时,如果勾选了用户认证页面中的Remember me on this computer选项,则当用户关闭浏览器之后,系统会记住该用户一段时间(由设置的有效期决定,本示例中是1800秒),如果在这段时间之内,当用户重新访问该系统时,用户不需要重新进行认证就已经是已认证的状态。

使用用户名admin和密码123进行用户认证时勾选上用户认证页面中的Remember me on this computer选项,然后重复执行关闭浏览器后再访问以下地址,并观察关闭了浏览器之后,再重新访问时是跳转到认证页面(未认证状态)还是直接跳转到了user.html页面(已认证状态):

http://localhost:8080/toUser

3.3 退出功能测试

用户认证成功之后,用户就处于已认证的状态,就可以在权限之内访问系统,此时可以通过访问以下地址请求退出已认证状态:

http://localhost:8080/logout

首先会跳转到退出确认页面(如下图所示),用户点击了Log Out按钮之后才会真正的执行退出操作,即回到未认证状态。
在这里插入图片描述

3.4 权限控制功能测试

用户在未认证的情况下通过浏览器访问以下地址请求toAdmin()方法(即访问admin.html页面),由于该方法要求用户已认证且具有ROLE_ADMIN权限才能访问,因此会自动跳转到用户认证页面。

http://localhost:8080/toAdmin

此时如果用用户名employee和密码123进行认证,认证成功之后由于该用户没有ROLE_ADMIN权限,因此会自动跳转到没有权限的页面(如下图所示):
在这里插入图片描述
如果用用户名admin和密码123进行认证,认证成功之后由于该用户拥有ROLE_ADMIN权限,因此会自动跳转到admin.html页面(如下图所示):
在这里插入图片描述
可见,确实起到了权限控制的作用。

4 原理分析

4.1 用户认证的过滤器链

Spring Security的用户认证流程是由一系列的过滤器链来实现的,默认的关于用户认证的过滤器链大致如下图所示:
在这里插入图片描述

  • SecurityContextPersistenceFilter: 在请求开始时,从配置好的 SecurityContextRepository 中获取 SecurityContext,并设置给 SecurityContextHolder。在请求完成后,把 SecurityContextHolder 所持有的SecurityContext 保存到配置好的 SecurityContextRepository,同时清除 securityContextHolder 所持有的 SecurityContext。

  • UsernamePasswordAuthenticationFilter: 用于处理来自表单提交的认证。该表单必须提供用户名和密码,其内部还有登录成功或失败后的处理器 AuthenticationSuccessHandler 和 AuthenticationFailureHandler。

  • ExceptionTranslationFilter: 能够捕获过滤器链中产生的所有异常,但只处理两类异常:AuthenticationException 和 AccessDeniedException,而其它的异常则继续抛出。
    如果捕获到的是 AuthenticationException,那么将会使用其对应的 AuthenticationEntryPoint 的commence()方法进行处理。在处理之前,ExceptionTranslationFilter会先使用 RequestCache 将当前的HttpServerletRequest的信息保存起来,以至于用户登录成功后可以跳转到之前的界面。
    如果捕获到的是 AccessDeniedException,那么将会根据当前访问的用户是否已经登录认证而做不同的处理,如果未登录,则使用关联的 AuthenticationEntryPoint 的 commence()方法进行处理,否则使用关联的 AccessDeniedHandler 的handle()方法进行处理。

  • FilterSecurityInterceptor: 用于保护HTTP资源的,它需要一个 AuthenticationManager 和一个 AccessDecisionManager 的引用。它会从 SecurityContextHolder 中获取 Authentication,然后通过 SecurityMetadataSource 可以得知当前请求是否在请求受保护的资源。对于请求那些受保护的资源,如果 Authentication.isAuthenticated() 返回false(即用户未认证),或者FilterSecurityInterceptor 的 alwaysReauthenticate 属性的值为 true,那么将会使用其引用的 AuthenticationManager 对Authentication进行认证,认证之后再使用认证后的 Authentication 替换 SecurityContextHolder 中原有的那个。然后使用 AccessDecisionManager 对用户当前请求进行权限检查。

4.2 用户认证的流程

Spring Security支持多种用户认证的方式,最常用的是基于用户名和密码的用户认证方式,其认证流程如下图所示:
在这里插入图片描述

4.3 “记住我” 功能的流程

用户可以使用账号和密码进行认证,但是如果用户使用账号和密码进行认证时选择了“记住我”功能,则在有效期内,当用户关闭浏览器后再重新访问服务时,不需要用户再次输入账号和密码重新进行认证,而是通过“记住我”功能自动认证。

“记住我”功能的认证流程如下图所示:
在这里插入图片描述
上述的用户认证处理逻辑都是基于Spring Security提供的默认实现,我们只需要自己实现一个UserDetailsService接口用于获取用户认证信息即可,十分简便。当然,Spring Security也能够支持我们使用自定义的用户认证处理逻辑,我们可以自己实现AuthenticationFilter和AuthenTicationProvider,以达到按照需求进行用户认证的目的。博主的另外一篇文章会专门分享自定义用户认证的实现。

4.4 权限控制的原理

Spring Security允许我们通过Spring EL权限验证表达式来指定访问URL或方法所需要的权限,用户在访问某个URL或方法时,如果对应的权限验证表达式返回结果为true,则表示用户拥有访问该URL或方法的权限,如果返回结果为false,则表示没有权限。Spring Security为我们提供了以下的权限验证表达式:

表达式描述
hasRole([role])当前用户是否拥有指定角色。
hasAnyRole([role1,role2])多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回true。
hasAuthority([auth])等同于hasRole
hasAnyAuthority([auth1,auth2])等同于hasAnyRole
Principle代表当前用户的principle对象
authentication直接从SecurityContext获取的当前Authentication对象
permitAll总是返回true,表示允许所有的
denyAll总是返回false,表示拒绝所有的
isAnonymous()当前用户是否是一个匿名用户
isRememberMe()表示当前用户是否是通过Remember-Me自动登录的
isAuthenticated()表示当前用户是否已经登录认证成功了。
isFullyAuthenticated()如果当前用户既不是一个匿名用户,同时又不是通过Remember-Me自动登录的,则返回true。

权限验证表达式只能验证用户是否具有访问某个URL或方法的权限,但是权限验证的这个步骤可以在不同的阶段进行。Spring Security中定义了以下四个支持使用权限验证表达式的注解,其中前两者可以用来在方法调用前或者调用后进行权限验证,后两者可以用来对集合类型的参数或者返回值进行过滤:

  • @PreAuthorize
  • @PostAuthorize
  • @PreFilter
  • @PostFilter

权限验证表达式需要和注解结合使用,示例如下所示:

@PreAuthorize("hasRole('ROLE_ADMIN')")
public void addUser(User user) {...
}@PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')")
public User find(int id) {return null;
}@PreAuthorize("#id<10")
public User find(int id) {return null;
}@PreAuthorize("principal.username.equals(#username)")
public User find(String username) {return null;
}@PreAuthorize("#user.name.equals('abc')")
public void add(User user) {...
}@PostAuthorize("returnObject.id%2==0")
public User find(int id) {...return user;
}@PostFilter("filterObject.id%2==0")
public List<User> findAll() {List<User> userList = new ArrayList<User>();...return userList;
}@PreFilter(filterTarget="ids", value="filterObject%2==0")
public void delete(List<Integer> ids, List<String> usernames) {...
}

5 总结

本文从使用方法和原理分析这两个方法简要的介绍了Spring Security的用户认证和权限控制这两大功能,但都是基于Spring Security的默认实现,我们也可以自定义用户认证和权限控制的实现逻辑,Spring Security用户认证和权限控制(自定义实现)详细介绍了用户认证相关和权限控制相关的自定义实现。关于授权服务器、资源服务器的内容可以查阅以下几篇文章:

OAuth2授权服务器和四种授权方式 这篇文章介绍了授权服务器和四种授权方式的配置与使用方法。
OAuth2资源服务器 这篇文章介绍了基于方法级别的权限控制的资源服务器的配置与使用方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/498066.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2017年数据可视化的七大趋势!

来源&#xff1a; 全球人工智能 概要&#xff1a;随着科技的不断进步与新设备的不断涌现&#xff0c;数据可视化领域目前正处在飞速地发展之中。 随着科技的不断进步与新设备的不断涌现&#xff0c;数据可视化领域目前正处在飞速地发展之中。ProPublica的调查记者兼开发者Lena…

python中for语句涉及的序列可以是关系表达式吗_为什么我可以在Python for循环中为迭代器和序列使用相同的名称?...

x告诉我们什么&#xff1a;Python 3.4.1 (default, May 19 2014, 13:10:29)[GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.40)] on darwinType "help", "copyright", "credits" or "license" for more information.>>>…

struts2中用interceptor实现权限控制

在jsp servlet中我们通常使用Servlet Filter控制用户是否登入&#xff0c; 是否有权限转到某个页面。在struts2中我们应该会想到他的拦截器(Interceptor)&#xff0c; Interceptor在struts2中起着非常重要的作用。很多struts2中的功能都是使用Interceptor实现的。 需求&#xf…

Java实现xml与map互转

此文档中包含单层和多层嵌套情况下&#xff0c;xml和map集合进行互转&#xff0c;具体代码如下&#xff1a; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry;import org.dom4j.Document; import org.dom4j.DocumentExce…

CGAL的三维点集

CGAL提供了几种处理点集的算法&#xff0c;从形状检测到通过标准点集处理工具进行的表面重建。 虽然这些算法不强制使用特定的数据结构&#xff0c;但该软件包提供了一个3D点集结构&#xff0c;使用户更容易处理附加属性&#xff0c;如法向量、颜色、标签&#xff0c;并在其上调…

2018年AI智商将达到多少?未来智能实验室启动第三次世界AI智商评测

来源&#xff1a; 人工智能学家 概要&#xff1a;21世纪以来&#xff0c;人工智能领域陆续爆发很多重要事件。其中最吸引人们眼球的&#xff0c;当属2016年战胜了人类围棋冠军并开始能够从0自我学习的AlphaGo。 一.人工智能能否超越人类智慧的争议 21世纪以来&#xff0c;人工智…

二进制函数_Go二进制文件逆向分析从基础到进阶——MetaInfo、函数符号和源码文件路径列表...

书接前文&#xff0c;本文主要介绍 Go 二进制文件中 Meta Information 的解析&#xff0c;与函数符号和源码文件路径列表的提取。最后详细介绍一下 Moduledata 这个结构。传送门&#xff1a;Go二进制文件逆向分析从基础到进阶——综述05Meta information>>>>5.1 Go…

学习网址

深入讲解权限&#xff1a; http://www.noahweb.net/mail/2/Project.htm#biaoStruts2 源码分析 http://blog.csdn.net/wl_ldy/article/details/5948779 Struts2部分源码讲解&#xff1a;http://code.google.com/p/struts2-src-study/source/browse/trunk/struts2-src-study--u…

七大科技巨头统治世界?

来源&#xff1a;亿欧智库 概要&#xff1a;我想无论是业内人士还是普通用户&#xff0c;都会思考为什么是他们成为最大的公司&#xff1f; 2017年秋天&#xff0c;随着腾讯和阿里巴巴两家中国公司市值的不断上涨&#xff0c;全球市值头部公司刚好是七大科技巨头&#xff08;下…

vue 懒人_Vue.js 中的实用工具方法【推荐】

收集日常开发中常用到的一些工具方法, 包含 vue 的公用过滤器、公用指令等 (PS: 懒人养成记)公用自定义过滤器import Vue from vueimport moment from moment/*** filter dateFormat 时间格式化* param {String, Date} value 可被 new Date 解析的字符串* param {String} forma…

用户权限管理——DB设计篇

来源&#xff1a;http://www.noahweb.net/mail/2/Project.htm#biao B/S系统中的权限比C/S中的更显的重要&#xff0c;C/S系统因为具有特殊的客户端&#xff0c;所以访问用户的权限检测可以通过客户端实现或通过客户端服务器检测实现&#xff0c;而B/S中&#xff0c;浏览器是每一…

oracle数据库常用的语法与复合函数

oracle查用到一些复合函数以及一些常用的方法用来快速查询数据&#xff0c;以下是我收集的一下常用方法&#xff0c;推荐给大家&#xff1a; 1、 当分组之后&#xff0c;针对某一属性值进行合并并以逗号进行分割&#xff1a; &#xff08;1&#xff09;所有版本都可使用&…

spring 事务之@transactional的使用与回滚

一、事务简单介绍 事务指逻辑上的一组操作&#xff0c;组成这组操作的各个单元&#xff0c;要不全部成功&#xff0c;要不全部不成功。 1.1 事务基本要素 原子性(Atomicity): 事务开始后所有操作&#xff0c;要么全部做完&#xff0c;要么全部不做&#xff0c;不可能停滞在…

python可视化图形界面_Python PyQt5 Designer 可视化图形界面模块

PYQT5 Designer简介强大的可视化GUI设计工具, 帮助我们快速开发PyQt.它生成UI界面为.ui文件, 通过命令将.ui转为.py文件.准备工作安装PyQt5: pip install pyqt5安装Qt工具: pip install pyqt5-tools(坑提示&#xff1a;一开始安装的pyqt5版本高了&#xff0c;结果安装QT工具的时…

雷克世界:Gyrfalcon加入芯片角斗场,又一款改变AI界的产品问世

来源&#xff1a;雷克世界 概要&#xff1a;随着人工智能产业规模扩大&#xff0c;众多巨头和初创公司纷纷加入人工智能芯片领域。 随着人工智能产业规模扩大&#xff0c;众多巨头和初创公司纷纷加入人工智能芯片领域&#xff0c;今天来了解一家旨在开发低成本、低功耗、高性能…

Freemarker静态化页面的使用

Freemarker 是一种基于模板的&#xff0c;用来生成输出文本的通用工具&#xff0c;所以我们必须要定制符合自己业务的模板&#xff0c;然后生成自己的文本&#xff08;html页面&#xff0c;string字符串&#xff0c;xml文本等等&#xff09;。Freemarker是通过freemarker.templ…

SessionHelper

问题描述&#xff1a; strut2 的织入 Session 为原始 Map 类型&#xff0c;没有泛型化&#xff0c;在添加属性时就会有一个恼人的警告。 功能&#xff1a; 1、安全的消除警告 2、插入时检查类型&#xff0c;如果不符就提前报错&#xff08;免得取值时才报转换异常的错误&…

报告怎么看_体检报告怎么看? 超实用的阅读指南来了!

体检报告怎么看&#xff1f;超实用的阅读指南来了&#xff01;要点概括除了禁食禁水可以吞口水吗&#xff1f;这样的问题&#xff0c;还有胆固醇、甘油三酯、胆红素…这些指标都是什么意思&#xff1f;出现升高或降低提示了怎样的身体变化&#xff1f;九图带你读懂&#xff01;…

从基础设施的演变,看人工智能到底需要什么样的底层平台

来源&#xff1a;亿欧 概要&#xff1a;大数据、大容量存储、弹性计算和各类算法的发展&#xff0c;尤其是在深度学习领域的发展&#xff0c;带来了各类脑洞大开的创新应用。 机器学习和人工智能的时代已经到来。大数据、大容量存储、弹性计算和各类算法的发展&#xff0c;尤其…