由于这次的项目需要对权限进行限制,所以在网上对security进行了学习,刚开始看的时候也是头脑非常混乱的,不过终归还是要一边写一边学,这样才能更好的理解,光是看是看不会的,接下来进入正题。
一开始看不懂没关系,等把整个代码写完,在各个地方打上断点再来对照这个图就能理清头绪
这里我一共使用到了4张表(t_manager,t_role,t_power,t_role_power)
-- ----------------------------
-- Table structure for t_manager
-- ----------------------------
DROP TABLE IF EXISTS `t_manager`;
CREATE TABLE `t_manager` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`manager_name` varchar(20) DEFAULT NULL,
`account` varchar(20) DEFAULT NULL,
`password` varchar(32) DEFAULT NULL,
`tel` varchar(13) DEFAULT NULL,
`email` varchar(100) DEFAULT NULL,
`fk_role_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_name` varchar(20) DEFAULT NULL,
`roleType` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_power
-- ----------------------------
DROP TABLE IF EXISTS `t_power`;
CREATE TABLE `t_power` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`power_name` varchar(50) DEFAULT NULL,
`url` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_role_power
-- ----------------------------
DROP TABLE IF EXISTS `t_role_power`;
CREATE TABLE `t_role_power` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`fk_role_power` bigint(20) DEFAULT NULL,
`fk_power_role` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=96 DEFAULT CHARSET=utf8;
表建好之后,导入security需要的jar包,然后开始配置
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/classes/spring-security.xmlparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<filter>
<filter-name>springSecurityFilterChainfilter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
filter>
<filter-mapping>
<filter-name>springSecurityFilterChainfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<http pattern="/jsp/power/userLogin.jsp" security="none" />
<http pattern="/jsp/power/managerLogin.jsp" security="none"/>
<http pattern="/login/imgCode" security="none"/>
<http pattern="/resource/**" security="none"/>
<http pattern="/jsp/power/error.jsp" security="none"/>
<http pattern="/jsp/common/*" security="none"/>
<http auto-config='true' access-denied-page="/jsp/power/error.jsp" use-expressions="true">
<form-login login-page="/jsp/power/userLogin.jsp" default-target-url="/login/controller"
always-use-default-target="true"/>
<custom-filter ref="securityFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
<custom-filter ref="ImgCodeFilter" before="FORM_LOGIN_FILTER"/>
http>
=
<beans:bean id="securityFilter" class="com.lovo.netctoss.filter.MySecurityFilter">
<beans:property name="authenticationManager" ref="myAuthenticationManager" />
<beans:property name="accessDecisionManager" ref="myAccessDecisionManager" />
<beans:property name="securityMetadataSource" ref="mySecurityMetadataSource" />
beans:bean>
<authentication-manager alias="myAuthenticationManager">
<authentication-provider user-service-ref="myUserDetailsService"/>
authentication-manager>
beans:beans>
<beans:bean id="ImgCodeFilter" class="com.lovo.netctoss.filter.ImgCodeFilter">
<beans:property name="authenticationManager" ref="myAuthenticationManager" />
<beans:property name="authenticationSuccessHandler">
<beans:bean class="org.springframework.security.web.authentication.SavedReq
uestAwareAuthenticationSuccessHandler">
<beans:property name="defaultTargetUrl" value="/login/controller">beans:property>
beans:bean>
beans:property>
<beans:property name="authenticationFailureHandler">
<beans:bean class="org.springframework.security.web.authentication.SimpleUrlA
uthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/jsp/power/userLogin.jsp">beans:property>
beans:bean>
beans:property>
beans:bean>
注:这个类用户登录使用的,并取得用户拥有的所有权限,通过用户名到数据库进行查询,如果没有查到这个用户,
则登录失败留在原网页,如果查出这个用户,则把用户的账号密码和查出的用户拥有的所有权限,封装到security
自己的User类中返回(得到的形式是这样的UserDetail: Username: wangnima; Password: [PROTECTED];
Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true;
Granted Authorities:ROLE_ADMIN),接下来security会把前台发过来的密码和查到的密码进行比较,
看是否登录成功,失败停留在原网页,这一步框架自己解决,不需要我们来判断,只需要返回User即可。
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Resource;
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.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.lovo.netctoss.operatesys.managermag.beans.ManagerBean;
import com.lovo.netctoss.operatesys.managermag.service.IManagerService;
@Service("myUserDetailsService")
public class MyUserDetailsService implements UserDetailsService{
@Resource
private IManagerService managerServiceImpl;
@Override
public UserDetails loadUserByUsername(String account)
throws UsernameNotFoundException {
// TODO Auto-generated method stub
ManagerBean manager = managerServiceImpl.selectManagerByAccount(account);
Set grantedAuths=obtionGrantedAuthorities(manager);
boolean enables = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
//封装成spring security的user
User userdetail = new User(manager.getManagerAccount(), manager.getManagerPwd(),
manager.getManagerName(), enables, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuths);
return userdetail;
}
//查找用户权限
public Set obtionGrantedAuthorities(ManagerBean manager){
Set authSet=new HashSet();
String [] powerNames = manager.getRole().getPowerLists().split(",");
for(String powerName:powerNames){
authSet.add(new SimpleGrantedAuthority(powerName));
}
return authSet;
}
}
注:我们发送的请求会进入mySecurityMetadataSource这个类中的getAttributes()方法中,
String requestUrl = ((FilterInvocation) object).getRequestUrl();可以得到我们刚才发送
请求的路径,然后通过loadResourceDefine()方法到数据库中查询所有的权限与对应可访问资源的关系,
因为项目管理的权限较少,所以这里采用的是权限和可访问资源之间1对1的关系,如果复杂的话可以采用1对多
,取出存入到resourceMap集合中(得到的是这样的{/jsp/user/*=[ROLE_USER], /jsp/power/*=
[ROLE_POWER],/jsp/expense/*=[ROLE_MONEY],/jsp/log/*=[ROLE_LOG],/jsp/manager/*=
[ROLE_MANAGER],/jsp/query/*=[ROLE_CHECK],/jsp/**=[ROLE_ADMIN]}),接下来验证用户请求的资
源是否需要某种权限,如果需要则返回相应的权限(也就是这一句return resourceMap.get(URLChangeTool.subString(requestUrl)))
这个地方通过请求url作为键得到的值是资源对应的权限集合,如果不需要则返回null。
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.Resource;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;
import com.lovo.netctoss.operatesys.powermag.dao.IRoleDao;
import com.lovo.netctoss.operatesys.powermag.dao.impl.RoleDaoImpl;
import com.lovo.netctoss.pojos.URLResource;
import com.lovo.netctoss.util.URLChangeTool;
@Service("mySecurityMetadataSource")
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource{
@Resource
private IRoleDao roleDaoImpl;
private static Map<String, Collection<ConfigAttribute>> resourceMap = null;
private List<URLResource> findResources;
//加载所有资源与权限的关系
private void loadResourceDefine() {
if(resourceMap==null){
resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
findResources = roleDaoImpl.findResource();
for(URLResource url_resources:findResources){
Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>();
//configAttribute放入权限名称(ROLE_xxxx 这种形式的,security默认的形式如果想改可以百度)
ConfigAttribute configAttribute = new SecurityConfig(url_resources.getRole_name());
configAttributes.add(configAttribute);
resourceMap.put(url_resources.getAccess_url(), configAttributes);
}
//以权限名封装为Spring的security Object
Set<Entry<String, Collection<ConfigAttribute>>> resourceSet = resourceMap.entrySet();
Iterator<Entry<String, Collection<ConfigAttribute>>> iterator = resourceSet.iterator();
}
}
//返回所请求资源所需要的权限
@Override
public Collection<ConfigAttribute> getAttributes(Object object)
throws IllegalArgumentException {
// TODO Auto-generated method stub
String requestUrl = ((FilterInvocation) object).getRequestUrl();
if(resourceMap == null) {
loadResourceDefine();
}
if(requestUrl.equals("/jsp/main/main.jsp")){
Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>();
for(URLResource url_resources:this.findResources){
ConfigAttribute configAttribute = new SecurityConfig(url_resources.getRole_name());
configAttributes.add(configAttribute);
}
return configAttributes;
}
return resourceMap.get(URLChangeTool.subString(requestUrl));
}
@Override
public boolean supports(Class> arg0) {
// TODO Auto-generated method stub
return true ;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
// TODO Auto-generated method stub
return null;
}
}
注:这个类在中间起到的作用是无论是从登录页面发送的请求,还是直接访问你想要的资源,都会被这个过滤器拦截
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
public class MySecurityFilter extends AbstractSecurityInterceptor implements Filter{
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
// TODO Auto-generated method stub
FilterInvocation fi=new FilterInvocation(req,res,chain);
invok(fi);
}
private void invok(FilterInvocation fi) throws IOException, ServletException{
InterceptorStatusToken token = null;
token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
@Override
public Class> getSecureObjectClass() {
// TODO Auto-generated method stub
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
// TODO Auto-generated method stub
return this.securityMetadataSource;
}
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return securityMetadataSource;
}
public void setSecurityMetadataSource(
FilterInvocationSecurityMetadataSource securityMetadataSource) {
this.securityMetadataSource = securityMetadataSource;
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
注authentication是用户拥有的权限,就是我们刚才返回User里面的,configAttributes是请求资源所需要的权限集合,
然后循环进行比较,我这里是只要有其中一个就可以通过,如果该用户没有这个权限则抛出AccessDeniedException异常,
这时后台会报错,但是会跳转到我们配置的失败页面中,这样一个流程就完成了。
import java.util.Collection;
import java.util.Iterator;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
@Service("myAccessDecisionManager")
public class MyAccessDecisionManager implements AccessDecisionManager{
@Override
public void decide(Authentication authentication, Object object,
Collection configAttributes) throws AccessDeniedException,
InsufficientAuthenticationException {
// TODO Auto-generated method stub
//如果请求的资源没有找到权限则放行,表示该资源为公共资源,都可以访问
if(configAttributes == null){
return ;
}
Iterator ite=configAttributes.iterator();
while(ite.hasNext()){
ConfigAttribute ca =ite.next();
String needRole = ca.getAttribute();
for(GrantedAuthority ga:authentication.getAuthorities()){
if(ga.getAuthority().equals("ROLE_ADMIN")){
return;
}
if(needRole.equals(ga.getAuthority())){
return;
}
}
}
throw new AccessDeniedException("没有权限!!!");
}
@Override
public boolean supports(ConfigAttribute arg0) {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean supports(Class> arg0) {
// TODO Auto-generated method stub
return true;
}
}
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
public class ImgCodeFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
String inputCode = request.getParameter("yzCode");
HttpSession session = request.getSession();
String generateCode = (String) session.getAttribute("code");
if(!inputCode.equals(generateCode)){
throw new AuthenticationServiceException("验证码错误!");
}
return super.attemptAuthentication(request, response);
}
}
这里要提一句的就是提交的路径必须是j_spring_security_check,输入框name必须是j_username和j_password,
这个是security规定好的
<form id="ff" action="/security/j_spring_security_check" onsubmit="return lgcheck()" method="post" >
<div style="margin-bottom:30px;padding-left:100px;color: rgba(151, 156, 249, 0.98) ">
<label style="font-size: 20px;">欢迎登录!label>
div>
<div style="margin-bottom:25px">
<input id="j_username" class="easyui-textbox" name="j_username" style="width:100%"
data-options="validType:'length[5,20]',label:'用户名:',required:true">
div>
<div style="margin-bottom:25px">
<input id="j_password" class="easyui-textbox" type="password" name="j_password" style="width:100%"
data-options="validType:'length[5,10]',label:'密码:',required:true">
div>
<div style="margin-bottom:25px;position: relative; ">
<input id="yzCode1" class="easyui-textbox" name="yzCode" style="width:55%" data-options="label:'验证码:'">
<img id="img" src="login/imgCode" style="width:30%; height: 20px;position: absolute; margin-left: 10px">
<img id="img2" onclick="javascript:rf()" src="resource/imgs/reload.png"
style="margin-left:100px;width:18px;">
div>
<input type="submit" value="登录" class="easyui-linkbutton" style="width: 78px;
height: 26px; float:left; margin-top: 5px;">
form>
URLResource类
public class URLResource {
private String role_name;//权限名
private String access_url;//该权限可以访问的资源
public String getRole_name() {
return role_name;
}
public void setRole_name(String role_name) {
this.role_name = role_name;
}
@Override
public String toString() {
return "URLResource [role_name=" + role_name + ", access_url="
+ access_url + "]";
}
public String getAccess_url() {
return access_url;
}
public void setAccess_url(String access_url) {
this.access_url = access_url;
}
public URLResource() {
super();
// TODO Auto-generated constructor stub
}
}
dao实现类中
public List findResource() {
ArrayList powers = roleServiceMapper.selectAllPower();
ArrayList urlRes = new ArrayList();
String urls = null;
for(PowerBean power :powers){
URLResource urlresources = new URLResource();
urls = power.getUrl();
urlresources.setRole_name(power.getPowerName());
urlresources.setAccess_url(urls);
urlRes.add(urlresources);
}
return urlRes;
最后是我自己写的转换路径的工具类仅供参考
public class URLChangeTool {
public static String subString(String reqUrl){
System.out.println(reqUrl);
if(reqUrl.matches("/[0-9a-zA-Z]{0,}") || reqUrl.matches("/jsp/[0-9a-zA-Z]{0,}")){
//任意返回一个需要权限的资源url,这样因为还未登录所以回到默认登录页面
return "/jsp/**";
}
return reqUrl.substring(0, reqUrl.lastIndexOf("/"))+"/*";
}
}