spring_security安全框架详解

本文是在项目中用到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
	

session监听器:

  
	    org.springframework.security.web.session.HttpSessionEventPublisher 
     


用户是怎样认证的?

在我们的安全系统中,当一个用户在我们的登录form中提供凭证后,这些凭证信息必须与凭证存储中的数据进行校验以确定下一步的行为,凭证的校验涉及到一系列的逻辑组件,它们封装了认证过程。

站在一个较高的层次上看,你可以看到有三个主要的组件负责这项重要的事情:

接口名                                         描述/角色

AbstractAuthenticationProcessingFilter 它在基于web的认证请求中使用。处理包含认证信息的请求,如认     证信息可能是form POST提交的, SSO信息或者其他用户提供的。创 建一个部分完整的 Authentication对象以在链中 传递凭证信息。

 

AuthenticationManager 它用来校验用户的凭证信息,或 者会抛出一个特定的异常(校验 失败的情况)或者完整填充 Authentication象,将会包含 了权限信息。

 

AuthenticationProvider 它为AuthenticationManager 提供凭证校验。一些 AuthenticationProvider的实 现基于凭证信息的存储,如数 库,来判定凭证信息是否可以被 认可。


spring_security_login是什么?我们怎么达到这个页面的?

URLspring_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 menus = Sets.newHashSet();
     for(Role role :roles){
    	 System.out.println(role.getRoleName());
    	 menus.addAll(role.getMenus());
     }
     List menusList = Lists.newArrayList() ;
     menusList.addAll(menus);
     user.setMenus(menusList);
     for (Menu menu : user.getMenus()) {
       if (menu.getNodeType().intValue() == 3) {
         authSet.add(new GrantedAuthorityImpl(menu.getPrefixedName()));
       }
     }
     user.setMenus(new ElintUtil().menuRelationSet(user.getMenus()));
     return authSet;
   }
 
   @Autowired
   public void setAccountManager(AccountManager accountManager) {
     this.accountManager = accountManager;
   }
 }

这个类通过实现UserDetailService来达到返回一个org.springframework.security.core.userdetails.UserDetails的目的。

下面是我的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;
	}
	
 }

有人可能要问了?为什么userDetails返回的是一个new LoginUser(userdetails, user)呢?

我们来看看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 ;
}
}

这样如果用户的用户名和密码都正确后,spring security就会把信息放入userDetails中,并且创建一个名叫"SPRING_SECURITY_CONTEXT"的session,我们可以通过下面的方式拿到Login对象。

 LoginUser user = (LoginUser)((SecurityContext)
			   ServletActionContext.getRequest().getSession().getAttribute(
			   "SPRING_SECURITY_CONTEXT")).getAuthentication()
			   .getPrincipal();

这样我们就可以在代码中拿到user对象进行一系列操作了。


下面我要讲一下项目中实现单点登录(一方登录,另一方再用同一帐号登录时,第一个用户被顶)的实现

我做单点登录的时候,网上很多教程很片面的说了一下spring security的配置,例如下面的配置:


			
		

可以这个配置好了之后,还是不起作用的,最根本的要去重写hashCode和equals方法,为什么呢?

因为第一个用户登录了之后,是把用户信息(帐号密码)放在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" %>

























可是每次被顶后,用户都直接跳到login.jsp去了,而没有走message.jsp,我就很苦恼。

于是我看了一下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);  
}  

ConcurrentSessionFilter

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);  
}  

这一句话的意思就是没有指定expired-url就会默认写出这么一段话
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跳走了呢?希望有人给我指出。



你可能感兴趣的:(Spring-Security)