简介
Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security为基于J2EE企业应用软件提供了全面安全服务。 特别是使用领先的J2EE解决方案-spring框架开发的企业软件项目。
安全方面的两个主要区域是“认证”和“授权”(或者访问控制),一般来说,Web 应用的安全性包括**用户认证(Authentication)和用户授权 ** **(Authorization)**两个部分,这两点也是 Spring Security 重要核心功能。
(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录
(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。
在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。
Spring Security与Shiro的比较
官方网站:
https://spring.io/projects/spring-security
Spring Security
-
和 Spring 无缝整合。
-
全面的权限控制。
-
专门为 Web 开发而设计。
◼旧版本不能脱离 Web 环境使用。
◼新版本对整个框架进行了分层抽取,分成了核心模块和 Web 模块。单独引入核心模块就可以脱离 Web 环境。
-
重量级。
Shiro
Apache 旗下的轻量级权限控制框架。Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
官网:https://shiro.apache.org/documentation.html
特点:
轻量级。Shiro 主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。
通用性。
好处:不局限于 Web 环境,可以脱离 Web 环境使用。
缺陷:在 Web 环境下一些特定的需求需要手动编写代码定制。
入门案例
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
编写Controller
@RestController
public class TestController {
@GetMapping("/")
public String hello(){
return "hello";
}
}
启动程序访问/
因安全框架的原因会将请求拦截自动跳转到认证页面,security默认提供的username为user,password在启动页会自动生成,输入默认账号密码认证成功后才会跳转显示hello!!!!
核心过滤器认识
在我们添加了SpringSecurity 依赖后,在项目的启动日志中,可以直观的看到SpringSecurity 的实现是通过filter chain实现的。
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFil
ter
org.springframework.security.web.context.SecurityContextPersistenceFilter
org.springframework.security.web.header.HeaderWriterFilter
org.springframework.security.web.csrf.CsrfFilter
org.springframework.security.web.authentication.logout.LogoutFilter
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter
org.springframework.security.web.session.SessionManagementFilter
org.springframework.security.web.access.ExceptionTranslationFilter
org.springframework.security.web.access.intercept.FilterSecurityInterceptor
FilterSecurityInterceptor :
动态实现根据用户访问的url进行权限管理
在动态实现根据访问的url进行权限认证时可以自定义FilterInvocationSecurityMetadataSource实现认证规则的配置(例根据用户请求url获取对应url的角色信息),修改FilterSecurityInterceptor自带MetadataSource,同时自定义AccessDecisionManager ,实现访问决策管理(根据登录用户查询到用户的角色,接着比对用户角色与当前url的角色,一致即可访问),最后在配置类中通过
http.authorizeRequests().withObjectPostProcessor(
new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setSecurityMetadataSource(new CustomerFilterInvocationSecurityMetadataSource());
object.setAccessDecisionManager(new CustomerAccessDecisionManger());
return object;
}
}
实现根据url动态认证授权。。。。。。
//一个位于底层的针对http安全控制的拦截器
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
Filter{
//.......
//核心方法 doFilter
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
//过滤器链执行
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
//进行过滤器链中过滤器的执行
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
//查看之前的filter是否通过
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
//调用服务执行filter过滤器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
}
UsernamePasswordAuthenticationFilter:
通过默认认证页输入用户名密码后,对/login的post请求做拦截,校验表单数据
//核心方法:
//默认用户名密码认证的url是/login,提交方式是post方式
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
// ~ Methods
// 默认提供的认证机制,默认只是获取到输入的用户名和密码进行比对,没有去查询数据库
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
核心接口
UserDetailsService
Security中加载用户数据的核心接口,当配置中没有账号密码时,通过实现此接口可以自定义逻辑(连接数据库)控制账号、密码认证。
在我们自定义服务时,实现此接口即可。
public interface UserDetailsService {
// ~ Methods
// ========================================================================================================
/**
* Locates the user based on the username. In the actual implementation, the search
* may possibly be case sensitive, or case insensitive depending on how the
* implementation instance is configured. In this case, the <code>UserDetails</code>
* object that comes back may have a username that is of a different case than what
* was actually requested..
*
*