Java Web系列:Spring Security 基础
Spring Security虽然比JAAS进步很大,但还是先天不足,达不到ASP.NET中的认证和授权的方便快捷。这里演示登录、注销、记住我的常规功能,认证上自定义提供程序避免对数据库的依赖,授权上自定义提供程序消除从缓存加载角色信息造成的角色变更无效副作用。
1.基于java config的Spring Security基础配置
(1)使用AbstractSecurityWebApplicationInitializer集成到Spring MVC
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}
(2)使用匿名类在WebSecurityConfigurerAdapter自定义AuthenticationProvider、UserDetailsService、SecurityContextRepository。
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/account**", "/admin**").authenticated();
http.formLogin().usernameParameter("userName").passwordParameter("password").loginPage("/login")
.loginProcessingUrl("/login").successHandler(new SavedRequestAwareAuthenticationSuccessHandler()).and()
.logout().logoutUrl("/logout").logoutSuccessUrl("/");
http.rememberMe().rememberMeParameter("rememberMe");
http.csrf().disable();
http.setSharedObject(SecurityContextRepository.class, new SecurityContextRepository() { private HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository(); @Override
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
SecurityContext context = this.repo.loadContext(requestResponseHolder);
if (context != null && context.getAuthentication() != null) {
Membership membership = new Membership();
String username = context.getAuthentication().getPrincipal().toString();
String[] roles = membership.getRoles(username);
context.getAuthentication().getAuthorities();
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username,
"password", convertStringArrayToAuthorities(roles));
context.setAuthentication(token);
System.out.println("check user role");
}
return context;
} @Override
public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
this.repo.saveContext(context, request, response);
} @Override
public boolean containsContext(HttpServletRequest request) {
return this.repo.containsContext(request);
}
});
} @Autowired
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new AuthenticationProvider() { @Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Membership membership = new Membership();
String username = authentication.getName();
String password = authentication.getCredentials().toString();
if (membership.validateUser(username, password)) {
String[] roles = membership.getRoles(username);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username,
"password", convertStringArrayToAuthorities(roles));
return token;
}
return null;
} @Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
} });
auth.userDetailsService(new UserDetailsService() { @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Membership membership = new Membership();
if (membership.hasUser(username)) {
UserDetails user = new User(username, "password",
convertStringArrayToAuthorities(membership.getRoles(username)));
return user;
}
return null;
}
});
} public Collection<? extends GrantedAuthority> convertStringArrayToAuthorities(String[] roles) {
List<SimpleGrantedAuthority> list = new ArrayList<SimpleGrantedAuthority>();
for (String role : roles) {
list.add(new SimpleGrantedAuthority(role));
}
return list;
}
}
2.使用@PreAuthorize在Controller级别通过角色控制权限
(1)使用@PreAuthorize("isAuthenticated()")注解验证登录
@PreAuthorize("isAuthenticated()")
@ResponseBody
@RequestMapping(value = "/account")
public String account() {
return "account";
}
(2)使用@PreAuthorize("hasAuthority('admin')")注解验证角色
@PreAuthorize("hasAuthority('admin')")
@ResponseBody
@RequestMapping("/admin")
public String admin() {
return "admin";
}
3.登录和注销功能
注销直接使用内置功能,登录可以自定义控制器和视图。
@RequestMapping(value = "/login")
public String login(@RequestParam(value = "error", required = false) String error,
@ModelAttribute("model") UserModel model, BindingResult result) {
if (error != null) {
result.rejectValue("userName", "", "Invalid username and password!");
}
return "login";
}
视图:
<%@ page language="java" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="s"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<!DOCTYPE HTML>
<html>
<head>
<title>Getting Started: Serving Web Content</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h2>Login</h2>
<form:form modelAttribute="model">
<s:bind path="*">
<c:if test="${status.error}">
<div id="message" class="error">Form has errors</div>
</c:if>
</s:bind>
<div>
<form:label path="userName">userName</form:label>
<form:input path="userName" />
<form:errors path="userName" cssClass="error" />
</div>
<div>
<form:label path="password">password</form:label>
<form:password path="password" />
<form:errors path="password" cssClass="error" />
</div>
<div>
<form:label path="rememberMe">rememberMe</form:label>
<form:checkbox path="rememberMe" />
</div>
<input type="submit" value="submit">
</form:form>
</html>
4.Spring Security核心对象
验证和授权的核心的ASP.NET肯定是HttpModule,Java是Filter,这没什么可说的,到现在两套组合(HttpApplicaiton+HttpModule+HttpHandler)(ServletContext+Filter+Servlet)的核心概念已经熟练了。
(1)安全上下文SecurityContext
ASP.NET中我们可以通过HttpContext.User获取IPrincipal的实例,这是通过HttpModuel(FormsAuthenticationModule)机制实现的。Spring Security中获取SecurityContext也是通过对应的Filter(SecurityContextPersistenceFilter)机制实现的。SecurityContextPersistenceFilter将功能委托给SecurityContextRepository的实例实现,因此我们在上文自定义了SecurityContextRepository实现,刷新其中的角色信息。
(2)身份认证提供程序AuthenticationProvider
AuthenticationProvider对象的authenticate方法验证并返回Authentication对象。Authentication对象是Java的Principal接口的子接口。上文自定义的AuthenticationProvider只是简单的将用户名username作为Authentication的实现类UsernamePasswordAuthenticationToken构造函数的参数传递,如果需要也可以传递其他object,调用时通过SecurityContextHolder.getContext().getAuthentication().getPrincipal()获取。
(3)用户信息提供程序UserDetailsService
只有在AuthenticationProvider的实现中采用了UserDetailsService用于验证,UserDetailsService才是必须的。UserDetailsService返回一个用UserDetails对象。在AuthenticationProvider中调用UserDetailsService,将UserDetails对象作为Principal参数传递给Authentication对象,这样我们可以在Controller中通过如下语句获取UserDetails对象。事实上只需要传递用户名作为Principal参数是最实用的,多搞一个自定义UserDetails还不如自定义POJO从Service中通过用户名返回信息来的干净快捷。即使使用UserDetails对象也不一定要使用UserDetailsService,可以直接在AuthenticationProvider中构造并传递UserDetails对象。上面代码的UserDetailsService只是作为演示,实际上不会被调用。
UserDetails userDetails =
(UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
参考
(1)http://docs.spring.io/autorepo/docs/spring-security/3.2.x/guides/hellomvc.html
(2)http://docs.spring.io/spring-security/site/docs/4.0.4.CI-SNAPSHOT/reference/htmlsingle/
(3)http://docs.spring.io/spring/docs/current/spring-framework-reference/html/view.html
JAAS对核心的数据结构不关注,定义了一堆还不如没有的过程依赖,Spring Security虽然改进了易用性,但从JAAS中继承了不切实际的幻想风格。提供核心数据结构和接口就行了,非要从密码加密到用户信息获取一路跑偏到对缓存和数据库都能产生依赖,Spring基于Object的依赖注入已经不适用了,Spring Security又偏离了核心。内置的不合理,外置的很难用,整合这个词就是整一堆框架合起来才能凑合用。什么时候基于类型的依赖注入框架能取代Spring、基于数据结构的验证框架能取代Spring Security,Java Web开发的生产力估计会提高一些。到现在我还没有找到可用的基于注解的前后端统一验证框架,没有这个东西,对于快速制作演示模型简直是灾难。不管怎么说,SSH这些东西至少要对其基础配置和核心对象有整体上的把握,至少要能快速定位开发中遇到的问题并基于对源代码的了解能应对实际中出现的大部分技术问题才行。
Java Web系列:Spring Security 基础的更多相关文章
- Java Web系列:JDBC 基础
ADO.NET在Java中的对应技术是JDBC,企业库DataAccessApplicationBlock模块在Java中的对应是spring-jdbc模块,EntityFramework在Java中 ...
- Java Web系列:Hibernate 基础
从以下5个方面学习hibernate ORM. (1)配置文件:hibernate.cfg.xml XML文件和hibernate.properties属性文件 (2)实体映射:1对多.多对多 (3) ...
- Java Web系列:Spring Boot 基础 Spring Security基本使用
@OneToOne or @ManyToOne Caused by: org.hibernate.AnnotationException: @OneToOne or @ManyToOne on com ...
- Java Web系列:Spring MVC基础
1.Web MVC基础 MVC的本质是表现层模式,我们以视图模型为中心,将视图和控制器分离出来.就如同分层模式一样,我们以业务逻辑为中心,把表现层和数据访问层代码分离出来是一样的方法.框架只能在技术层 ...
- Java Web系列:Spring Boot 基础
Spring Boot 项目(参考1) 提供了一个类似ASP.NET MVC的默认模板一样的标准样板,直接集成了一系列的组件并使用了默认的配置.使用Spring Boot 不会降低学习成本,甚至增加了 ...
- Java Web系列:Spring Boot 基础 (转)
Spring Boot 项目(参考1) 提供了一个类似ASP.NET MVC的默认模板一样的标准样板,直接集成了一系列的组件并使用了默认的配置.使用Spring Boot 不会降低学习成本,甚至增加了 ...
- Java Web系列:Spring依赖注入基础
一.Spring简介 1.Spring简化Java开发 Spring Framework是一个应用框架,框架一般是半成品,我们在框架的基础上可以不用每个项目自己实现架构.基础设施和常用功能性组件,而是 ...
- Java Web系列:Java Web 项目基础
1.Java Web 模块结构 JSP文件和AXPX文件类似,路径和URL一一对应,都会被动态编译为单独class.Java Web和ASP.NET的核心是分别是Servlet和IHttpHandle ...
- 【JavaEE】SSH+Spring Security基础上配置AOP+log4j
Spring Oauth2大多数情况下还是用不到的,主要使用的还是Spring+SpringMVC+Hibernate,有时候加上SpringSecurity,因此,本文及以后的文章的example中 ...
随机推荐
- python3 关联规则Apriori代码模版
#!/usr/bin/env python3 # -*- coding: utf-8 -*- from numpy import * def loadDataSet(): return [['a', ...
- javascript如何判断手机端的浏览器是否支持触碰功能
if(document.hasOwnProperty("ontouchstart")) alert("浏览器支持触屏"); else alert("浏 ...
- [Python] numpy.sum
import numpy as np #Syntax: numpy.sum(a, axis=None, dtype=None, out=None, keepdims=<class numpy._ ...
- ubuntu sudo apt-get update与sudo apt-get upgrade的作用及区别,以及python pip的安装
在UBUNTU下,我们维护一个源列表,源列表里面都是一些网址信息,这每一条网址就是一个源,这个地址指向的数据标识着这台源服务器上有哪些软件可以安装使用.编辑源命令: sudo gedit /etc/a ...
- Unix socket的准备(一)
套接字地址结构 套接字编程中,五元组是广为人知的. (host_ip, host_port, target_ip, target_port, protocol). 其中 ip 和 port 就是由套接 ...
- 用TImageList动态画透明图片
procedure TForm1.Button1Click(Sender: TObject); var bm : TBitmap; il : TImageList; begin bm := TBitm ...
- Shder中实现TintColor
[Shder中实现TintColor] TintColor实现上相当于一个滤镜,若TintColor的R为0,则原图的R通道颜色应该为0.基于此,实现TintColor很容易,原图颜色直接乘以Tint ...
- Python any() 函数
Python any() 函数 Python 内置函数 描述 any() 函数用于判断给定的可迭代参数 iterable 是否全部为 False,则返回 False,如果有一个为 True,则返回 ...
- javascript的构造函数和实例对象、prototype和__proto__的区别,原型对象及构造器的理解
一.前言 我们先通过代码来分别打印出实例对象.构造函数,以及修改了原型对象的构造函数,通过对比内部结构来看看他们之间的区别. //定义构造函数 function Person(name, age){ ...
- react-native 组件的导入、导出
一.前言背景: 学习react native的关键在于组件,依靠组件的拼接达到想要的效果,由此可见,组件就像一块块功能各异的零件,最终搭建出我们想要的效果. 今天我们就从组件的导入.导出开始 下面是我 ...