[SpringSecurity] 入门简介

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Konaji/article/details/81751071

前言

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

接下来我们先来个入门案例,先学会简单实用 SpringSecurity

入门案例

在 pom.xml 中引入 Spring 以及 SpringSecurity

        <!--SpringSecurity-->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>4.1.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>4.1.0.RELEASE</version>
        </dependency>
        <!--SpringMVC-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

配置web.xml,引入SpringSecurity的Filter

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
         http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-security.xml</param-value>
    </context-param>
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
    <!--配置SpringSecurity的拦截器-->
    <filter>
        <!--注: 名称必须是 springSecurityFilterChain -->
        <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-app>

配置一个最简单的 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/security
       http://www.springframework.org/schema/security/spring-security.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--页面拦截规则-->
    <!--
        注 :
            use-expressions : 是否使用表达式
            1>use-expressions=false时,access可以只写ROLE_USER
            2>use-expressions=true时,access得写成hasRole('ROLE_USER')
    -->
    <http use-expressions="true">
        <!--
            pattern : 要拦截的资源
            /*  : 表示的是该目录下的资源,只包括本级目录资源
            /** : 表示的是该目录及该目录下所有子目录的资源
        -->
        <intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
        <!--
            配置登录页,如果什么都不配置的话,使用的是Spring-Security默认的登录页
            默认登录页的路径是 : /login
        -->
        <form-login/>
    </http>

    <!--认证管理器-->
    <authentication-manager>
        <authentication-provider>
            <user-service>
                <!--配置一个默认的用户,并赋予一个权限-->
                <user name="admin" password="123456" authorities="ROLE_USER" />
            </user-service>
        </authentication-provider>
    </authentication-manager>

</beans:beans>

现在我们启动Maven工程,访问 http://localhost:8888/security2/login
`

配置 spring-security.xml

1> 修改默认登陆页

新增一个登录页 login.html

注:必须用POST提交到login

<!-- 注:必须用POST提交到login-->
<form action='login' method='POST'>
    <table>
        <tr>
            <td>用户名:</td>
            <td><input type='text' name='user' value=''></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type='password' name='pass' /></td>
        </tr>
        <tr>
            <td colspan='2'><input name="submit" type="submit"
                                   value="登录" /></td>
        </tr>
    </table>
</form>

删除之前的

<http use-expressions="true">
    <!--删除此项-->
    <!--<form-login/>-->
</http>

修改为

<http use-expressions="true">
    <!--
            配置登录页 :
            login-page : 指定登录页地址 (登录页提交时必须用POST提交到/login)
            default-target-url : 指定登陆成功后进入的页面
            always-use-default-target : 一直使用该登陆地址
            authentication-failure-url : 登录错误后跳转地址
            username-parameter : 登录页表单中用户名的参数名称(input的name)
                                 若不指定,参数名称必须是username
            password-parameter : 登录页表单中密码的参数名称(input的name)
                                 若不指定,参数名称必须是password
        -->
        <!--注:配置了登录页之后一定要配置该页面允许匿名访问,否则是访问不到的-->
        <form-login login-page="/login.html" default-target-url="/index.html"
                    always-use-default-target="true"
                    authentication-failure-url="/index.html"
                    username-parameter="user" password-parameter="pass"/>
</http>

允许/login.html匿名访问

<!--匿名访问-->
<!--注:此项是有顺序的,最好放到最上面-->
<http pattern="/login.html" security="none"></http>

2> CSRF

此时就可以继续启动Maven工程访问了,但是这时会给我们报一个403 CSRF 错误

HTTP Status 403 - Could not verify the provided CSRF 
token because your session was not found.

CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。

注:CSRF一般只拦截POST请求,因为POST请求会改数据,GET最多是获取数据

解决方案

修改 spring-security.xml

<http use-expressions="true">
    <!--关闭防CSRF攻击-->
    <csrf disabled="true"/>
</http>

此时就可以继续登录了
`

3> 登出

修改 spring-security.xml

<http use-expressions="true">
    <!--
        登出 :
         logout-url : 指定登出的URL,不指定默认是/logout
         logout-success-url : 指定登出后跳转到的页面
    -->
    <logout logout-url="/logout" logout-success-url="/login.html"/>
</http>

4> iframe

SpringSecurity默认是会拦截 iframe 的引入,如果页面中引入了 ifame,应开启该选项

<http use-expressions="true">
        <headers>
            <!--
                默认是policy="DENY"是拒绝的
                ALLOW-FROM允许
                SAMEORIGIN同源当前项目的资源
            -->
            <frame-options policy="SAMEORIGIN"></frame-options>
        </headers>
</http>

自定义认证类

通过以上配置我们发现登录时的账号、密码、权限都是写死在 spring-security.xml 里的

现在我们配置一个自定义认证类,来实现动态查询数据库登录

创建类UserDetailsServiceImpl.java 实现UserDetailsService接口

@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    //@Reference
    //private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        // username -> 用户页面提交的账号
        // select db ...
        // 相关权限
        List<GrantedAuthority> lists = new ArrayList<>();
        lists.add(new SimpleGrantedAuthority("ROLE_USER"));
        return new User(username,"123456",lists);
    }
}

修改 spring-security.xml 的认证配置

<!--开启Spring扫包,或者用bean注入userDetailsServiceImpl-->
<context:component-scan base-package="com.sino"/>
<authentication-manager>
        <authentication-provider user-service-ref="userDetailsServiceImpl">
        </authentication-provider>
</authentication-manager>

这样我们就可以实现自定义认证了

SpringSecurity权限注解

SpringSecutity 在权限控制中有多个注解

@PreAuthorize,@PostAuthorize,@Secured,@RolesAllowed

@PreAuthorize,@PostAuthorize 是方法级注解,分别在方法调用前后执行权限检查

部分注解 @PreAuthorize,@PostAuthorize 支持 Spring EL 表达式

例如: hasRole, hasAnyRole, hasPermission

修改 spring-security.xml 开启注解

<global-method-security
        jsr250-annotations="enabled"
        pre-post-annotations="enabled"
        secured-annotations="enabled">
</global-method-security>
  • prePostEnabled :决定Spring Security的前注解是否可用 [@PreAuthorize,@PostAuthorize,..]
  • secureEnabled : 决定是否Spring Security的保障注解 [@Secured] 是否可用
  • jsr250Enabled :决定 JSR-250 annotations 注解[@RolesAllowed..] 是否可用.

接下来为了便于测试,我们先把SpringMVC配置一下,修改web.xml,增加DispatcherServlet

    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 指定加载的配置文件 ,通过参数contextConfigLocation加载-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-security.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

修改 spring-security.xml 开启MVC

    <context:component-scan base-package="com.sino"/>
    <mvc:annotation-driven/>
    <mvc:default-servlet-handler/>

测试Controller

@RestController
public class TestController {
    @RequestMapping("/test")
    @PreAuthorize("hasRole('ROLE_USER1')")
    public String test(){
        return "test";
    }
}

Permission

SpringSecurity 的 permission 默认是返回 false,如果要使用 hasPermission 进行权限检查,

我们要写一个自定义类实现 PermissionEvaluator 放入 DefaultMethodSecurityExpressionHandler

public class MyPermissionEvaluator implements PermissionEvaluator {

    @Override
    public boolean hasPermission(Authentication authentication,
                                 Object targetDomainObject, Object permission) {
        if (targetDomainObject.equals("user")) {
            return this.hasPermission(authentication, permission);
        }
        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication,
                                 Serializable serializable, String s, Object o) {
        return false;
    }

    /**
     * 匹配权限
     */
    private boolean hasPermission(Authentication authentication, Object permission) {
        Collection<? extends GrantedAuthority> authorities
                = authentication.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            if (authority.getAuthority().equals(permission)) {
                return true;
            }
        }
        return false;
    }
}

修改 spring-security.xml

    <!--注:class里的内容要改成一行-->
    <beans:bean id="expressionHandler"
                class="org.springframework.security.access.
                expression.method.DefaultMethodSecurityExpressionHandler">
        <beans:property name="permissionEvaluator" ref="myPermissionEvaluator" />
    </beans:bean>
    <!-- 自定义的PermissionEvaluator实现 -->
    <beans:bean id="myPermissionEvaluator"
                class="com.sino.service.MyPermissionEvaluator"/>

    <global-method-security
            jsr250-annotations="enabled"
            pre-post-annotations="enabled"
            secured-annotations="enabled">
        <!--引入expressionHandler-->
        <expression-handler ref="expressionHandler" />
    </global-method-security>

测试Controller

@RestController
public class TestController {
    @RequestMapping("/test")
    @PreAuthorize("hasPermission('user','ROLE_USER')")
    public String test(){
        return "test";
    }
}

完整代码

通过以上介绍,SpringSecurity 的入门简介就说完了,下面是所有代码

pom.xml

    <properties>
        <spring.version>4.2.4.RELEASE</spring.version>
    </properties>

    <dependencies>
        <!--SpringSecurity-->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>4.1.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>4.1.0.RELEASE</version>
        </dependency>
        <!--Spring-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
         http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-security.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!--配置SpringSecurity的拦截器-->
    <filter>
        <!--注: 名称必须是 springSecurityFilterChain -->
        <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>

    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 指定加载的配置文件 ,通过参数contextConfigLocation加载-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-security.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

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"
             xmlns:context="http://www.springframework.org/schema/context"
             xmlns:mvc="http://www.springframework.org/schema/mvc"
             xsi:schemaLocation="http://www.springframework.org/schema/security
       http://www.springframework.org/schema/security/spring-security.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="com.sino"/>
    <mvc:annotation-driven/>
    <mvc:default-servlet-handler/>

    <!--匿名访问-->
    <http pattern="/login.html" security="none"></http>

    <!--页面拦截规则-->
    <!--
        注 :
            use-expressions : 是否使用表达式
            1>use-expressions=false时,access可以只写ROLE_USER
            2>use-expressions=true时,access得写成hasRole('ROLE_USER')
    -->
    <http use-expressions="true">
        <!--
            pattern : 要拦截的资源
            /*  : 表示的是该目录下的资源,只包括本级目录资源
            /** : 表示的是该目录及该目录下所有子目录的资源
        -->
        <intercept-url pattern="/**" access="hasRole('ROLE_USER')" />

        <!--
            配置登录页 :
            login-page : 指定登录页地址 (登录页提交时必须用POST提交到/login)
            default-target-url : 指定登陆成功后进入的页面
            always-use-default-target : 一直使用该登陆地址
            authentication-failure-url : 登录错误后跳转地址
            username-parameter : 登录页表单中用户名的参数名称(input的name)
                                 若不指定,参数名称必须是username
            password-parameter : 登录页表单中密码的参数名称(input的name)
                                 若不指定,参数名称必须是password
        -->
        <!--注:配置了登录页之后一定要配置该页面允许匿名访问,否则是访问不到的-->
        <form-login login-page="/login.html" default-target-url="/index.html"
                    always-use-default-target="true"
                    authentication-failure-url="/index.html"
                    username-parameter="user" password-parameter="pass"/>
        <!--关闭CSRF-->
        <csrf disabled="true"/>
        <!--开启iframe-->
        <headers>
            <!--
                默认是policy="DENY"是拒绝的
                ALLOW-FROM允许
                SAMEORIGIN同源当前项目的资源
            -->
            <frame-options policy="SAMEORIGIN"></frame-options>
        </headers>
        <!--
            登出 :
            logout-url : 指定登出的URL,不指定默认是/logout
            logout-success-url : 指定登出后跳转到的页面
        -->
        <logout logout-url="/logout" logout-success-url="/login.html"/>
    </http>

    <!--认证管理器-->
    <authentication-manager>
        <authentication-provider user-service-ref="userDetailsServiceImpl">
        </authentication-provider>
    </authentication-manager>

    <!--注:class里的内容要改成一行-->
    <beans:bean id="expressionHandler"
                class="org.springframework.security.access.
                expression.method.DefaultMethodSecurityExpressionHandler">
        <beans:property name="permissionEvaluator" ref="myPermissionEvaluator" />
    </beans:bean>
    <!-- 自定义的PermissionEvaluator实现 -->
    <beans:bean id="myPermissionEvaluator"
                class="com.sino.service.MyPermissionEvaluator"/>
    <!--
        prePostEnabled :
            决定Spring Security的前注解是否可用 [@PreAuthorize,@PostAuthorize]
        secureEnabled :
            决定是否Spring Security的保障注解 [@Secured] 是否可用
        jsr250Enabled :
            决定 JSR-250 annotations 注解[@RolesAllowed..] 是否可用.
    -->
    <global-method-security
            jsr250-annotations="enabled"
            pre-post-annotations="enabled"
            secured-annotations="enabled">
        <expression-handler ref="expressionHandler" />
    </global-method-security>

</beans:beans>

MyPermissionEvaluator.java

public class MyPermissionEvaluator implements PermissionEvaluator {

    @Override
    public boolean hasPermission(Authentication authentication,
                                 Object targetDomainObject, Object permission) {
        if (targetDomainObject.equals("user")) {
            return this.hasPermission(authentication, permission);
        }
        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication,
                                 Serializable serializable, String s, Object o) {
        return false;
    }

    /**
     * 匹配权限
     */
    private boolean hasPermission(Authentication authentication, Object permission) {
        Collection<? extends GrantedAuthority> authorities
                = authentication.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            if (authority.getAuthority().equals(permission)) {
                return true;
            }
        }
        return false;
    }
}

UserDetailsServiceImpl.java

@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    //@Reference
    //private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        // username -> 用户页面提交的账号
        // select db ...
        // 相关权限
        List<GrantedAuthority> lists = new ArrayList<>();
        lists.add(new SimpleGrantedAuthority("ROLE_USER"));
        return new User(username,"111111",lists);
    }
}

TestController.java

@RestController
public class TestController {
    @RequestMapping("/test")
    //@PreAuthorize("hasRole('ROLE_USER')")
    @PreAuthorize("hasPermission('user','ROLE_USER')")
    public String test(){
        return "test";
    }
}

至此,SpringSecurity 的入门简介就说完了~

展开阅读全文

没有更多推荐了,返回首页