SpringSecurity基础

SpringSecurity基础

Spring Security是什么?

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

Spring Security能做什么?

Spring Security 提供了一套完善的java权限管理体系,从登陆到权限的验证,而且它很好的发挥了spring的特点(基于配置),方便管理维护,在web研发过程中非常的实用。

简单的说SpringSecurity主要做两件事 1.认证 2.授权

<!--more-->

Spring Security如何使用?

因为Spring Security有很多使用方式,所以我这里只讲开发中经常用的自定义的方式,也就是最灵活的方式

首先创建一个Spring MVC项目,为什么不用SpringBoot? 因为我觉得 既然都用上Spring Security 基本不会用SpringBoot。

在项目中加入需要的依赖

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-core</artifactId>
  <version>3.2.4.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-web</artifactId>
  <version>3.2.4.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-config</artifactId>
  <version>3.2.4.RELEASE</version>
</dependency>

<dependency>
  <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>3.2.4.RELEASE</version>
 </dependency>

在Web.xml中加入SpringSecurity的拦截器

<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

如果你是像我一样自定义配置文件路径的话也要在Web.xml重写ContextLoaderListener

<!--添加前端控制器-->
<!--添加ContextLoaderListener   ContextLoaderListener默认会加载/WEB-Info/applicationContext.xml这个Spring配置文件-->
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--重写默认配置实现记载多配置文件-->
<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>
      classpath:Spring-Security.xml,
      classpath:Spring-resources.xml
  </param-value>
</context-param>

创建 登陆页 错误页 还有受保护页(可以理解为 登陆后才可以进入页面)

以下是 登陆页 错误页 主页

@RequestMapping("/login")
public String toLogin(){
    System.out.println("登陆");
    System.out.println();
    return "tologin";
}

@RequestMapping("/sorry")
public String toSorry(){
    System.out.println("sorry");
    System.out.println();
    return "sorry";
}
@RequestMapping("index")
public String toIndex(){
    System.out.println("index");
    System.out.println();
    return "index";
}

受保护资源1 admin 才能访问sayHi

@Controller
@RequestMapping("/admin")
public class TestController {

    @RequestMapping("/sayHi")
    public String toTestJSP(Map<String,Object> map){
        String sayHi="Hi,My is Spring MVC";
        map.put("sayHi",sayHi);
        return "test";
    }
}

受保护资源2 user才能访问的sayHi

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/sayHi")
    public String toTestJSP(Map<String,Object> map){
        return "user";
    }
}

至此为止 一切都已经准备好了,下面是SpringSecurity内容 为什么不用数据库,因为我懒得用数据库了身份信息都用死值定义的。

创建Spring-Security.xml

<?xml version="1.0" encoding="UTF-8"?>
<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.2.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd">

讲Beans 定义为父元素 不然每次都要写太麻烦了

<!--登陆页不拦截-->
<http pattern="/login" security="none" />
<!--拦截 admin 所有请求-->
<http  access-denied-page="/sorry">
    <form-login
            login-page="/login"
            default-target-url="/login"
            authentication-failure-url="/login?login_error=t"  />
    <session-management>
        <concurrency-control max-sessions="1" error-if-maximum-exceeded="false"/>
    </session-management>
    <!--         <remember-me key="elim" user-service-ref="securityManager"/> -->
    <custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="securityInterceptor"/>
</http>

<beans:bean id="securityInterceptor" class="filter.MyFilterSecurityInterceptor">
    <beans:property name="authenticationManager" ref="authenticationManager" />
    <beans:property name="accessDecisionManager" ref="myAccessDecisionManagerBean" />
    <beans:property name="securityMetadataSource" ref="securityMetadataSource" />
</beans:bean>


<authentication-manager alias="authenticationManager">
    <authentication-provider user-service-ref="msecurityManager">
        <!--如果用户的密码采用加密的话 <password-encoder hash="md5" /> -->
    </authentication-provider>
</authentication-manager>
<!--在这个类中,你就可以从数据库中读入用户的密码,角色信息,是否锁定,账号是否过期等 -->
<beans:bean id="msecurityManager" class="security.SecurityManagerSupport"/>
<!--访问决策器,决定某个用户具有的角色,是否有足够的权限去访问某个资源 -->
<beans:bean id="myAccessDecisionManagerBean"
       class="security.MyAccessDecisionManager">
</beans:bean>
<!--资源源数据定义,将所有的资源和权限对应关系建立起来,即定义某一资源可以被哪些角色访问 -->
<beans:bean id="securityMetadataSource"
        class="security.MyInvocationSecurityMetadataSource" />

以上是Spring Security配置文件

access-denied-page 当权限不足跳转的页面

login-page 登陆页

default-target-url 默认页

authentication-failure-url 密码失败跳转的页面(我这里密码失败跳回登陆页 携带额外参数 页面判断是否有额外参数 从而提示用户是否密码错误)

session-management 中可以定义很多元素比如 检测Session超时 concurrency-control session 固定攻击保护

error-if-maximum-exceeded 若当前maximumSessions为1,当设置为true表示同一账户登录会抛出SessionAuthenticationException异常,异常信息为:Maximum sessions of {0} for this principal exceeded

custom-filter Spring Security 的底层是通过一系列的 Filter 来管理的,每个 Filter 都有其自身的功能,而且各个 Filter 在功能上还有关联关系,所以它们的顺序也是非常重要的。

通过 position、before 或者 after 指定该 Filter 放置的位置

FILTER_SECURITY_INTERCEPTOR代表Http

具体如下

别名Filter 类对应元素或属性
CHANNEL_FILTERChannelProcessingFilterhttp/intercept-url@requires-channel
SECURITY_CONTEXT_FILTERSecurityContextPersistenceFilterhttp
CONCURRENT_SESSION_FILTERConcurrentSessionFilterhttp/session-management/concurrency-control
LOGOUT_FILTERLogoutFilterhttp/logout
X509_FILTERX509AuthenticationFilterhttp/x509
PRE_AUTH_FILTERAstractPreAuthenticatedProcessingFilter 的子类
CAS_FILTERCasAuthenticationFilter
FORM_LOGIN_FILTERUsernamePasswordAuthenticationFilterhttp/form-login
BASIC_AUTH_FILTERBasicAuthenticationFilterhttp/http-basic
SERVLET_API_SUPPORT_FILTERSecurityContextHolderAwareRequestFilterhttp@servlet-api-provision
JAAS_API_SUPPORT_FILTERJaasApiIntegrationFilterhttp@jaas-api-provision
REMEMBER_ME_FILTERRememberMeAuthenticationFilterhttp/remember-me
ANONYMOUS_FILTERAnonymousAuthenticationFilterhttp/anonymous
SESSION_MANAGEMENT_FILTERSessionManagementFilterhttp/session-management
EXCEPTION_TRANSLATION_FILTERExceptionTranslationFilterhttp
FILTER_SECURITY_INTERCEPTORFilterSecurityInterceptorhttp
SWITCH_USER_FILTERSwitchUserFilter
<beans:bean id="securityInterceptor" class="filter.MyFilterSecurityInterceptor">
    <beans:property name="authenticationManager" ref="authenticationManager" />
    <beans:property name="accessDecisionManager" ref="myAccessDecisionManagerBean" />
    <beans:property name="securityMetadataSource" ref="securityMetadataSource" />
</beans:bean>

这段配置文件相信熟悉Spring的人已经明白 将几个对象手动填充到类中

package filter;

import javax.servlet.*;

import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
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;

import java.io.IOException;

public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter{

    private FilterInvocationSecurityMetadataSource securityMetadataSource;

    Log log= LogFactory.getLog(this.getClass());

    public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
        return securityMetadataSource;
    }

    public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource securityMetadataSource) {
        this.securityMetadataSource = securityMetadataSource;
    }

    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

    //登陆后,每次访问资源都通过这个拦截器拦截
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
        invoke(fi);
    }


    private void invoke(FilterInvocation fi) throws IOException, ServletException {
        //fi里面有一个被拦截的url
        //里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
        //再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
        //它首先会调用MyInvocationSecurityMetadataSource类的getAttributes方法获取被拦截url所需的权限,在调用MyAccessDecisionManager类decide方法判断用户是否够权限
        System.out.println("===Fi="+fi.getFullRequestUrl()+"===");
        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
            //执行下一个拦截器
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }

    public void init(FilterConfig filterConfig) throws ServletException {

    }
    public void destroy() {

    }
}

这个Filter是我们的核心 拦截器根据URL 拦截用户是否有该权限

解释这三个 对象都是做什么的

1.authenticationManager

定义用户信息 角色信息 账号是否过期等

<authentication-manager alias="authenticationManager">
    <authentication-provider user-service-ref="msecurityManager">
        <!--如果用户的密码采用加密的话 <password-encoder hash="md5" /> -->
    </authentication-provider>
</authentication-manager>
<!--在这个类中,你就可以从数据库中读入用户的密码,角色信息,是否锁定,账号是否过期等 -->
<beans:bean id="msecurityManager" class="security.SecurityManagerSupport"/>

也就是这一段

public class SecurityManagerSupport implements UserDetailsService {
    //登陆验证时,通过username获取用户的所有权限信息,
    //并返回User放到spring的全局缓存SecurityContextHolder中,以供授权器使用
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //因为太麻烦 所以不打算连数据库
        List<GrantedAuthority> auths=new ArrayList<GrantedAuthority>();
        //模拟从数据库中取出对必须是ROLE_开头的,不然spring security不认账的
        SimpleGrantedAuthority auth1=new SimpleGrantedAuthority("ROLE_ADMIN");
        SimpleGrantedAuthority auth2=new SimpleGrantedAuthority("ROLE_USER");
        System.out.println("===userName"+username+"===");
        if(username.equals("admin")){
            auths=new ArrayList<GrantedAuthority>();
            auths.add(auth1);
        }else if(username.equals("user")){
            auths=new ArrayList<GrantedAuthority>();
            auths.add(auth2);
        }
        //                  账号      密码
        User user=new User(username,"yanhao",true,true,true,true,auths);
        return user;
    }
}

我这里 手动创建了两个角色 User和Admin 如果有数据库的话这里可以做成一个SQL 通过用户名查询 用户的权限 并将这个用户return

定义完 角色权限信息 接下来是定义 某一个权限 能访问那些资源 securityMetadataSource

public class MyInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource{
    Log log = LogFactory.getLog(this.getClass());
    private static Map<String, Collection<ConfigAttribute>> resourceMap = null;

    public MyInvocationSecurityMetadataSource() {
        loadResourceDefine();
    }
    private PathMatcher matcher;

    private void loadResourceDefine() {
        matcher= new AntPathMatcher();
        resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
        Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
        ConfigAttribute ca = new SecurityConfig("ROLE_ADMIN");
        atts.add(ca);
        resourceMap.put("/admin/sayHi", atts);


        Collection<ConfigAttribute> attsno =new ArrayList<ConfigAttribute>();
        ConfigAttribute cano = new SecurityConfig("ROLE_USER");
        attsno.add(cano);
        resourceMap.put("/user/sayHi", attsno);
    }



    //参数是要访问的url,返回这个url对于的所有权限(或角色)
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        // 将参数转为url
        String url = ((FilterInvocation)object).getRequestUrl();
        Iterator<String> ite = resourceMap.keySet().iterator();
        while (ite.hasNext()) {
            String resURL = ite.next();
            System.out.println("===URL="+url+"RESURL="+resURL+"===");
            if (matcher.match(url, resURL)) {
                System.out.println("Yes Yes Yes");
                return resourceMap.get(resURL);
            }
        }
        System.out.println("No No No");
        return null;
    }

    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    public boolean supports(Class<?> aClass) {
        return true;
    }
}

这里我在构造函数中 定义了 权限 admin能访问admin/sayHi User可以访问User/sayHi

实现 getAttributes 判断请求的URL 是否是定义的

最后就剩下accessDecisionManager访问决策器 定义完了 用户信息 和身份信息 最后就差判断了

public class MyAccessDecisionManager implements AccessDecisionManager{

    Log log= LogFactory.getLog(this.getClass());

    //检查用户是否够权限访问资源
    //参数authentication是从spring的全局缓存SecurityContextHolder中拿到的,里面是用户的权限信息
    //参数object是url
    //参数configAttributes所需的权限
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        if (collection == null) {
            return;
        }
        Iterator<ConfigAttribute> ite = collection.iterator();
        while (ite.hasNext()) {
            ConfigAttribute ca = ite.next();
            String needRole = ((SecurityConfig) ca).getAttribute();
            for (GrantedAuthority ga : authentication.getAuthorities()) {
                if (needRole.equals(ga.getAuthority())) {
                    return;
                }
            }
        }
        //注意:执行这里,后台是会抛异常的,但是界面会跳转到所配的access-denied-page页面
        throw new AccessDeniedException("no right");
    }

    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    public boolean supports(Class<?> aClass) {
        return true;
    }
}

这里就是 判断访问的URL 是否是该用户拥有的资源

到此为止 SpringSecurtiy就配置完成了

大致流程就是 用户访问URL 进入拦截器,拦截器 拦截用户请求 判断用户身份是否通过,如果通过判断用户所访问的资源 是否是 有权访问的,如果是 继续执行其它拦截器 如果不是 跳转到error页

转载于:https://my.oschina.net/yanhaohub/blog/3065924

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值