本文是在项目中用到Spring Security3来进行登录验证时才进行学习的,写的比较片面,请大家提出问题。
优秀的安全框架连接:
http://blog.csdn.net/z69183787/article/details/38542109
http://blog.csdn.net/z69183787/article/details/38542125
http://blog.csdn.net/z69183787/article/details/38542139
首先要在web.xml中配置
contextConfigLocation
classpath*:/applicationContext.xml
classpath*:/applicationContext-security.xml
加载security配置文件
加入过滤器:
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
REQUEST
FORWARD
org.springframework.security.web.session.HttpSessionEventPublisher
用户是怎样认证的?
在我们的安全系统中,当一个用户在我们的登录form中提供凭证后,这些凭证信息必须与凭证存储中的数据进行校验以确定下一步的行为,凭证的校验涉及到一系列的逻辑组件,它们封装了认证过程。
站在一个较高的层次上看,你可以看到有三个主要的组件负责这项重要的事情:
接口名 描述/角色
AbstractAuthenticationProcessingFilter 它在基于web的认证请求中使用。处理包含认证信息的请求,如认 证信息可能是form POST提交的, SSO信息或者其他用户提供的。创 建一个部分完整的 Authentication对象以在链中 传递凭证信息。
AuthenticationManager 它用来校验用户的凭证信息,或 者会抛出一个特定的异常(校验 失败的情况)或者完整填充 Authentication对象,将会包含 了权限信息。
AuthenticationProvider 它为AuthenticationManager 提供凭证校验。一些 AuthenticationProvider的实 现基于凭证信息的存储,如数据 库,来判定凭证信息是否可以被 认可。
spring_security_login是什么?我们怎么达到这个页面的?
URL的spring_security_login部分表明这是一个默认的登录页面并且是在DefaultLoginPageGeneratingFilter中命名的,我们可以使用配置属性来修改这个页面的名字从而使它对我们应用来说是唯一的。
建议修改登录页URL的默认值,修改后不仅能够对应用或搜索引擎更友好,而且能够隐藏你使用Spring Security做为安全实现的事实。
下面就是一段登录页面login.jsp的代码:
这是一个利用spring安全框架登录的页面,点击提交后,会检查用户名和密码是否为所需正则,再进行提交,提交会根据配置文件进行认证匹配
下面给出application-security.xml
SpringSecurity安全配置
认证配置中的authentication-manager是对用户输入的信息进行验证的操作,我们通过实现UserDetailService进行实现。
下面是UserDetailServiceImpl代码图
package com.wiseweb.pom.service.account;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.wiseweb.pom.entity.Role;
import com.wiseweb.pom.entity.Menu;
import com.wiseweb.util.ElintUtil;
import com.wiseweb.util.LoginUser;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.transaction.annotation.Transactional;
@Transactional(readOnly=false)
public class UserDetailsServiceImpl
implements UserDetailsService {
private AccountManager accountManager;
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException{
com.wiseweb.pom.entity.User user = this.accountManager.findUniqueUser(username);
if (user == null) {
throw new UsernameNotFoundException("用户" + username + " 不存在");
}
@SuppressWarnings("rawtypes")
Set grantedAuths = obtainGrantedAuthorities(user);
boolean enabled = true;//是不是激活的
boolean accountNonExpired = true;//账户是否过期
boolean credentialsNonExpired = true;//认证是否过期
boolean accountNonLocked = true;//是否锁定
@SuppressWarnings("unchecked")
UserDetails userdetails = new org.springframework.security.core.userdetails.User(
user.getLoginName(), user.getPassWord(), enabled,
accountNonExpired, credentialsNonExpired, accountNonLocked,
grantedAuths);
return new LoginUser(userdetails, user);
}
@SuppressWarnings("unchecked")
private Set obtainGrantedAuthorities(com.wiseweb.pom.entity.User user)
{
@SuppressWarnings("rawtypes")
Set authSet = Sets.newHashSet();
List roles = user.getRoles();
Set
下面是我的User实体类:
package com.wiseweb.pom.entity;
import com.google.common.collect.Lists;
import com.wiseweb.pom.entity.IdEntity;
import com.wiseweb.pom.entity.Menu;
import com.wiseweb.pom.entity.Department;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
@SuppressWarnings("unused")
@Entity
@Table(name="wise_user")
public class User extends IdEntity
{
private static final long serialVersionUID = 1L;
private String loginName;
private String name;
private String passWord;
private String userStatus;
private String email;
private String telephone;
private Integer sex;
private Date lastLoginTime ;
private String userSign ;
private Integer orderType ;
private List roles = Lists.newArrayList() ;
private Integer flag ;
private List menus = Lists.newArrayList();
private List homeMenus = new ArrayList() ;
private Department department = null;
public User()
{
}
public User(String loginName, String name, String passWord, String userStatus, String email, String telephone)
{
this.loginName = loginName;
this.name = name;
this.passWord = passWord;
this.userStatus = userStatus;
this.email = email;
this.telephone = telephone;
}
public User(String loginName, String name, String passWord, String userStatus, String email, String telephone,Integer sex)
{
this.loginName = loginName;
this.name = name;
this.passWord = passWord;
this.userStatus = userStatus;
this.email = email;
this.telephone = telephone;
this.sex = sex;
}
@Column(name="login_name", nullable=false, length=45)
public String getLoginName() {
return this.loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
@Column(name="name", nullable=false, length=45)
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
@Column(name="pass_word", nullable=false, length=45)
public String getPassWord() {
return this.passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
@Column(name="user_status", nullable=false, length=45)
public String getUserStatus() {
if(this.userStatus != null){
return this.userStatus;
}else{
return "1";
}
}
public void setUserStatus(String userStatus) {
this.userStatus = userStatus;
}
@Column(name="email", length=45)
public String getEmail() {
return this.email;
}
public void setEmail(String email) {
this.email = email;
}
@Column(name="telephone", length=45)
public String getTelephone() {
return this.telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
@Column(name="sex")
public Integer getSex() {
if(this.sex != null){
return sex;
}else{
return 1;
}
}
public void setSex(Integer sex) {
this.sex = sex;
}
public void setMenus(List menus) {
this.menus = menus;
}
@Transient
public List getMenus() {
return this.menus;
}
@Transient
public List getHomeMenus() {
return this.homeMenus;
}
public void setHomeMenus(List homeMenus) {
this.homeMenus = homeMenus;
}
@Column(name="last_login_time", nullable=false, length=45)
public Date getLastLoginTime() {
return lastLoginTime;
}
public void setLastLoginTime(Date lastLoginTime) {
this.lastLoginTime = lastLoginTime;
}
@Column(name="user_sign", length=45)
public String getUserSign() {
return userSign;
}
public void setUserSign(String userSign) {
this.userSign = userSign;
}
@Column(name="order_type")
public Integer getOrderType() {
return orderType;
}
public void setOrderType(Integer orderType) {
this.orderType = orderType;
}
@Column(name="flag", nullable=false)
public Integer getFlag() {
return flag;
}
public void setFlag(Integer flag) {
this.flag = flag;
}
@ManyToOne
@JoinColumn(name="depart_id")
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
// @ManyToMany
// @JoinTable(name="wise_role_user", joinColumns={@javax.persistence.JoinColumn(name="user_id")}, inverseJoinColumns={@javax.persistence.JoinColumn(name="role_id")})
// @Fetch(FetchMode.SUBSELECT)
// @Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
@ManyToMany
@JoinTable(name = "wise_role_user", joinColumns = { @javax.persistence.JoinColumn(name = "user_id") }, inverseJoinColumns = { @javax.persistence.JoinColumn(name = "role_id")})
@Fetch(FetchMode.SUBSELECT)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public List getRoles() {
return roles;
}
public void setRoles(List roles) {
this.roles = roles;
}
}
我们来看看LoginUser代码
package com.wiseweb.util;
import com.wiseweb.pom.entity.HomeMenu;
import com.wiseweb.pom.entity.Role;
import com.wiseweb.pom.entity.User;
import com.wiseweb.pom.entity.Menu;
import com.wiseweb.pom.entity.Department;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
public class LoginUser implements UserDetails{
private static final long serialVersionUID = 1366695319372075545L;
private UserDetails userDetails;
private User user;
public LoginUser(UserDetails userDetails, User user)
{
this.userDetails = userDetails;
this.user = user;
}
public Collection getAuthorities()
{
return this.userDetails.getAuthorities();
}
public String getPassword()
{
return this.userDetails.getPassword();
}
public String getUsername()
{
return this.userDetails.getUsername();
}
public boolean isAccountNonExpired()
{
return this.userDetails.isAccountNonExpired();
}
public boolean isAccountNonLocked()
{
return this.userDetails.isAccountNonLocked();
}
public boolean isCredentialsNonExpired()
{
return this.userDetails.isCredentialsNonExpired();
}
public boolean isEnabled()
{
return this.userDetails.isAccountNonExpired();
}
public UserDetails getUserDetails() {
return this.userDetails;
}
public String getLoginName() {
return this.user.getLoginName();
}
public String getName() {
return this.user.getName();
}
//by lib
public Long getUserId() {
return this.user.getId();
}
public String getPassWord() {
return this.user.getPassWord();
}
public String getUserStatus() {
return this.user.getUserStatus();
}
public String getEmail() {
return this.user.getEmail();
}
public String getTelephone() {
return this.user.getTelephone();
}
public List getMenus() {
return this.user.getMenus();
}
public Long getId() {
return this.user.getId();
}
public Integer getSex(){
return this.user.getSex() ;
}
public Date lastLoginName(){
return this.user.getLastLoginTime() ;
}
public String userSign(){
return this.user.getUserSign() ;
}
public Integer getOrderType(){
return this.user.getOrderType() ;
}
public List getRoles(){
return this.user.getRoles() ;
}
public Integer getFlag(){
return this.user.getFlag() ;
}
public Department getDepartment(){
return this.user.getDepartment() ;
}
public List getHomeMenus(){
return this.user.getHomeMenus() ;
}
public User getUser() {
return user;
}
@Override
public int hashCode() {
return this.userDetails.getUsername().hashCode() ;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof LoginUser){
return this.userDetails.getUsername().equals(((LoginUser)obj).getUsername());
}
return false ;
}
}
LoginUser user = (LoginUser)((SecurityContext)
ServletActionContext.getRequest().getSession().getAttribute(
"SPRING_SECURITY_CONTEXT")).getAuthentication()
.getPrincipal();
下面我要讲一下项目中实现单点登录(一方登录,另一方再用同一帐号登录时,第一个用户被顶)的实现
我做单点登录的时候,网上很多教程很片面的说了一下spring security的配置,例如下面的配置:
因为第一个用户登录了之后,是把用户信息(帐号密码)放在userDetails中的,第二个用户再进行登录后,他的信息又会放入userDetails中,这时候就要在放入的时候进行比较,如果相同则告诉spring容器有相同的帐号登录了,就请求spring踢除第一个用户。
所以我们必须比较userDetails中的username,那么在哪里比较呢,我这个例子是在LoginUser中比较的,当然大家实现的不一样,原理其实都一样。
下面是最核心的一段代码:
@Override
public int hashCode() {
return this.userDetails.getUsername().hashCode() ;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof LoginUser){
return this.userDetails.getUsername().equals(((LoginUser)obj).getUsername());
}
return false ;
}
接着又来了一个问题,我要做成像腾讯qq那样,重复登录后及时被顶并且提示“您的账户已在异地登录”之类的提示语,告诉用户
下面是我一开始的配置
这是message.jsp代码
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ include file="/common/taglibs.jsp" %>
<%@ page import="org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter" %>
<%@ page import="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter" %>
<%@ page import="org.springframework.security.web.WebAttributes" %>
于是我看了一下SessionmanagerFilter和ConcurrentSessionFilter的源码,认为我的配置是正确的。
下面是两个源码,也可以去我的博客去看全文:http://blog.csdn.net/benjamin_whx/article/details/39204699
SessionManagerFilter:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
//省略……
//判断当前session中是否有SPRING_SECURITY_CONTEXT属性
if (!securityContextRepository.containsContext(request)) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && !authenticationTrustResolver.isAnonymous(authentication)) {
try {
//再通过sessionStrategy执行session固化、并发处理
//与UsernamePasswordAuthenticationFilter时处理一样,后面会仔细分析。
sessionStrategy.onAuthentication(authentication, request, response);
} catch (SessionAuthenticationException e) {
SecurityContextHolder.clearContext();
failureHandler.onAuthenticationFailure(request, response, e);
return;
}
//把SecurityContext设置到当前session中
securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response);
} else {
if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {
if (invalidSessionUrl != null) {
request.getSession();
redirectStrategy.sendRedirect(request, response, invalidSessionUrl);
return;
}
}
}
}
chain.doFilter(request, response);
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession(false);
if (session != null) {
//这个SessionInformation是在执行SessionManagementFilter时通过sessionRegistry构造的并且放置在map集合中的
SessionInformation info = sessionRegistry.getSessionInformation(session.getId());
//如果当前session已经注册了
if (info != null) {
//如果当前session失效了
if (info.isExpired()) {
// Expired - abort processing
//强制退出
doLogout(request, response);
//目标url为expired-url标签配置的属性值
String targetUrl = determineExpiredUrl(request, info);
//跳转到指定url
if (targetUrl != null) {
redirectStrategy.sendRedirect(request, response, targetUrl);
return;
} else {
response.getWriter().print("This session has been expired (possibly due to multiple concurrent " +
"logins being attempted as the same user).");
response.flushBuffer();
}
return;
} else {
// Non-expired - update last request date/time
//session未失效,刷新时间
info.refreshLastRequest();
}
}
}
chain.doFilter(request, response);
}
response.getWriter().print("This session has been expired (possibly due to multiple concurrent " +
"logins being attempted as the same user).");
可是我还是没有走message.jsp,带着疑问我把session-manager中的invalid-session-url="login.jsp"去掉了,这样就可以了,是不是spring先判断走SessionManagerFilter?如果session为空就直接根据invalid-session-url跳走了呢?希望有人给我指出。