在 Spring MVC + Spring 项目中使用 Spring Security 4.2.3

Spring Security 为基于Java EE 的企业软件应用程序提供全面的安全服务(官方)。使用 Spring Securituy 可以方便的对一个 Web 进行用户登录认证、权限控制。可以通过配置文件方式和Java方式进行配置。以下是两种方式:

准备

1 Maven 坐标

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>4.2.3.RELEASE</version>
</dependency>
<dependency> 
    <groupId>org.springframework.security</groupId> 
    <artifactId>spring-security-config</artifactId> 
    <version>4.2.3.RELEASE</version> 
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-taglibs</artifactId>
    <version>4.2.3.RELEASE</version>
</dependency>

2 在 web.xml 中配置 Spring Security 过滤器

<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>

方式一 配置文件方式

1 添加配置文件 spring-security.xml,并在 web.xml 中扫描此配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd">

    <!-- 注意要在 web.xml 中扫描此配置文件 -->

    <!-- 读取错误提示属性文件,实现自定义提示。原文件位置 spring-security-core-4.2.3.RELEASE.jar 包中 org/springframework/security/messages_zh_CN.properties 
         可以将其内容拷贝到自定义的属性文件中,修改相关的提示信息,将 basenames 属性值指向自定义属性文件
    -->
    <!-- <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basenames" value="classpath:org/springframework/security/messages_zh_CN"></property>
    </bean> -->

    <!-- security="none":对指定的 URL 放行,不拦截。如一些静态文件。另外放行登录 URL 避免拦截,造成无法登录 -->
    <security:http security="none" pattern="/login" />
    <security:http security="none" pattern="/staticfile/**" />

    <security:http auto-config="false" use-expressions="true" access-decision-manager-ref="">
        <!--
            login-page:表示自定义登录页面
            login-processing-url:表示登录时提交的地址
            username-parameter:表示登录时用户名使用的是哪个参数
            password-parameter:表示登录时密码使用的是哪个参数
            default-target-url:
                默认情况下,在登录成功后会返回到原本受限制的页面
                如果用户是直接请求登录页面,登录成功后默认情况下会跳转到当前应用的根路径,即欢迎页面
                default-target-url 属性可以指定,用户直接访问登录页面并登陆成功后跳转的页面
                如果想让用户不管是直接请求登录页面,还是通过 Spring Security 引导过来的,登录之后都跳转到指定的页面,可以使用 always-use-default-target 属性为 true 来达到这一效果
            authentication-success-handler-ref:
                对应一个 AuthencticationSuccessHandler 实现类的引用
                登录认证成功后会调用指定 AuthenticationSuccessHandler 的 onAuthenticationSuccess 方法,在此方法中进行登陆成功后的处理
                此时 default-target-url 失效
            authentication-failure-url:
                指定登录认证失败后跳转的页面
                默认情况下登录失败后会返回登录页面
                登录失败后跳转的页面,也需放行,否则又会被重定向到登录页面。
            authentication-failure-handler-ref:
                对应一个用于处理认证失败的 AuthenticationFailureHandler 实现类。
                指定了该属性,Spring Security 在认证失败后会调用指定 AuthenticationFailureHandler 的 onAuthenticationFailure 方法对认证失败进行处理
                此时 authentication-failure-url 属性将不再发生作用。
         -->
        <security:form-login login-page="/login"
                            login-processing-url="/user/doLogin"
                            username-parameter="username"
                            password-parameter="password"
                            authentication-success-handler-ref="authenticationSuccessHandlerImpl"
                            authentication-failure-handler-ref="authenticationFailureHandlerImpl" />

        <security:logout logout-success-url="/login"
                        logout-url="/user/logout" 
                        invalidate-session="true" 
                        delete-cookies="JSESSIONID" />

        <!-- 设置访问所有的 URL 都必须登录 -->
        <security:intercept-url pattern="/**" access="isAuthenticated()" />

        <!-- 
            access="hasRole('ROLE_ADMIN')":表示拥有 ADMIN 角色的用户可以访问,否则 403。
                hasRole('ROLE_ADMIN') 为 SpEL 表达式,必须以 ROLE_ 开头
         -->
        <security:intercept-url pattern="/user/**" access="hasRole('ROLE_0')"/>

        <!-- 指定登陆认证成功后,用户访问未授权的 URL 将跳转的 URL -->
        <security:access-denied-handler error-page="/error/403"/>

        <security:session-management session-fixation-protection="none">
            <!-- 
                max-sessions="1":同一用户只能在一个浏览器登录,当尝试在其他浏览器登陆时将被拒绝
                error-if-maximum-exceeded="true":当设置了此属性,尝试在其他浏览器登录时,则原会话将被终止,将在新窗口建立新会话
            -->
            <security:concurrency-control max-sessions="1"/>
        </security:session-management>

        <security:csrf disabled="true" />
    </security:http>

    <!-- 认证成功后的处理类 -->
    <bean id="authenticationSuccessHandlerImpl" class="diary.security.AuthenticationSuccessHandlerImpl"/>
    <!-- 认证失败后的处理类 -->
    <bean id="authenticationFailureHandlerImpl" class="diary.security.AuthenticationFailureHandlerImpl"/>

    <!-- 登录认证 -->
    <security:authentication-manager>
        <!-- 直接将用户名密码卸载配置文件中
        <security:authentication-provider>
            <security:user-service>
                <security:user name="user" password="user" authorities="ROLE_USER" />
                <security:user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" />
            </security:user-service> 
        </security:authentication-provider>
        -->
        <!-- 使用自定义的类对用户提交的密码进行加密操作,实现 AuthenticationSuccessHandler 接口 -->
        <security:authentication-provider user-service-ref="userDetailsServiceImpl">
            <security:password-encoder ref="myMessageDigestPasswordEncoder"/>
        </security:authentication-provider>
    </security:authentication-manager>

    <bean id="myMessageDigestPasswordEncoder" class="diary.security.MyMessageDigestPasswordEncoder">
        <constructor-arg name="algorithm" value="md5"/> 
    </bean>

    <bean id="userDetailsServiceImpl" class="diary.security.UserDetailsServiceImpl"></bean>

</beans>

2 配置文件中使用到的自定义类

2.1 UserDetailsServiceImpl

import java.util.List;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import diary.mapper.UserMapper;
import diary.pojo.User;

/**
 * 根据用户提交的用户名查询出用户信息
 */
public class UserDetailsServiceImpl implements UserDetailsService {
    @Resource
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if(StringUtils.isEmpty(username)) {
            throw new BadCredentialsException("用户名不能为空");
        }

        UserDetails userDetails = null;
        // 根据用户名从数据库查询用户信息,根据自己的业务规则去写
        User user = this.userMapper.getUserByUsername(username);
        if(user == null) {
            throw new BadCredentialsException("用户名不存在");
        }
        userDetails = new org.springframework.security.core.userdetails.User(
                user.getUsername(), 
                user.getPassword(), // 数据库中存储的密码    
                true,               // 用户是否激活
                true,               // 帐户是否过期
                true,               // 证书是否过期
                true,               // 账号是否锁定
                AuthorityUtils.createAuthorityList("ROLE_" + user.getType()));  // 用户角色列表,必须以 ROLE_ 开头
        return userDetails;
    }
}

2.2 MyMessageDigestPasswordEncoder

import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder;

import diary.common.util.MD5Util;

/**
 * 用于对用户提交的密码进行自定义的加密操作
 */
public class MyMessageDigestPasswordEncoder extends MessageDigestPasswordEncoder {

    public MyMessageDigestPasswordEncoder(String algorithm) {
        super(algorithm);
    }

    /**
     * encPass:用户的真是密码
     * raw:用户提交的密码
     * 
     */
    @Override
    public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
        if(StringUtils.isEmpty(rawPass)) {
            throw new BadCredentialsException("密码不能为空");
        }
        return encPass.equals(MD5Util.md5(rawPass));
    }

}

2.3 AuthenticationSuccessHandlerImpl

import java.io.IOException;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import diary.mapper.UserMapper;
import diary.pojo.User;

/**
 * 用于登录认证成功后执行的操作
 */
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
    @Resource
    private UserMapper userMapper;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
        // UserDetails 中存放着用户名等信息
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        // 获取该用户信息,根据自己的业务规则写
        User user = this.userMapper.getUserByUsername(username);
        // 将用户放到 Session
        request.getSession().setAttribute("currUser", user);
        // 跳转到主页
        response.sendRedirect(request.getContextPath() + "/home.html");
    }

}

2.4 AuthenticationFailureHandlerImpl

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

/**
 * 用于用户登录认证失败后执行的操作
 */
public class AuthenticationFailureHandlerImpl implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException {
        // AuthenticationException 存放着异常信息,获取出来,放到 Request 中,转发到登录页面。
        request.setAttribute("error", exception.getMessage());
        request.getRequestDispatcher("/login").forward(request, response);
    }

}

方式二 注解+Java 代码配置方式

此类相当于上述的 spring-security.xml,其中的各种配置都可以对应到 spring-security.xml 中的配置项。四个自定义类不变。需要强调的时必须在 Spring 的配置文件中扫描此类所在的包。

package diary.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import diary.security.AuthenticationFailureHandlerImpl;
import diary.security.AuthenticationSuccessHandlerImpl;
import diary.security.MyMessageDigestPasswordEncoder;
import diary.security.UserDetailsServiceImpl;

/**
 * 注解方式配置 Spring Security,注意需要在 Spring 配置文件中扫描此类
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 设置不拦截规则
        web.ignoring().antMatchers("/login", "/error/**", "/css/**", "/help/**", "/img/**", "/js/**", "/res/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 自定义 accessDecisionManager 访问控制器,并开启表达式语言
        http.authorizeRequests()
                .expressionHandler(webSecurityExpressionHandler())          // 启用 SpEL 表达式
                .antMatchers("/user/**").hasRole("0")                       // 访问 /user/** 必须拥有角色 "0",在使用标签时 <security:authorize access="hasRole('ROLE_0')">
                .and().exceptionHandling().accessDeniedPage("/error/403")   // 指定登陆认证成功后,用户访问未授权的 URL 将跳转的 URL
                .and().authorizeRequests().anyRequest().authenticated();    // 指定所有的请求都需登录

        // 开启默认登录页面
        // http.formLogin();

        // 自定义登录页面
        http.formLogin().loginPage("/login")                        // 指定登录页面
            .loginProcessingUrl("/user/doLogin")                    // 执行登录操作的 URL
            .usernameParameter("username")                          // 用户请求登录提交的的用户名参数
            .passwordParameter("password")                          // 用户请求登录提交的密码参数
            .failureHandler(this.authenticationFailureHandler())    // 定义登录认证失败后执行的操作
            .successHandler(this.authenticationSuccessHandler());   // 定义登录认证曾工后执行的操作

        // 自定义注销
        http.logout().logoutUrl("/user/logout")                     // 执行注销操作的 URL
            .logoutSuccessUrl("/login")                             // 注销成功后跳转的页面
            .invalidateHttpSession(true)
            .deleteCookies("JSESSIONID");

        // session 管理
        http.sessionManagement().sessionFixation().none().maximumSessions(1);

        // 禁用 CSRF 
        http.csrf().disable();
    }

    /**
     * 登录认证配置
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(this.userDetailsService())          
            .passwordEncoder(this.messageDigestPasswordEncoder());  
    }

    /**
     * 使用自定义的登录密码加密规则,需继承  MessageDigestPasswordEncoder
     */
    @Bean(name = "myMessageDigestPasswordEncoder")
    public MessageDigestPasswordEncoder messageDigestPasswordEncoder() {
        return new MyMessageDigestPasswordEncoder("md5");
    }

    /**
     * 使用自定义的登录认证失败处理类,需继承 AuthenticationFailureHandler
     */
    @Bean(name = "authenticationFailureHandlerImpl")
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return new AuthenticationFailureHandlerImpl();
    }

    /**
     * 使用自定义的登录认证成功处理类,需继承 AuthenticationSuccessHandler
     */
    @Bean(name = "authenticationSuccessHandlerImpl")
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return new AuthenticationSuccessHandlerImpl();
    }

    @Bean(name = "userDetailsServiceImpl")
    public UserDetailsService userDetailsService() {
        return new UserDetailsServiceImpl();
    }

    // 表达式控制器
    @Bean(name = "expressionHandler")
    public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() {
        return new DefaultWebSecurityExpressionHandler();
    }

}

另附

在前端页面可以使用 Spring Security 提供的标签控制元素的显示。
可参考 http://blog.csdn.net/running_snail_/article/details/7167771
一个简单的例子如下:

<%-- 只有拥有角色“0”的用户才能看到<a>标签 --%>
<security:authorize access="hasRole('ROLE_0')">
    <a href="/user/list"></a>
</security:authorize>

猜你喜欢

转载自blog.csdn.net/li90hou/article/details/77851845