美文网首页spring cloud我爱编程IT技术篇
《Spring Boot极简教程》第16章 Spring Boo

《Spring Boot极简教程》第16章 Spring Boo

作者: 光剑书架上的书 | 来源:发表于2017-04-17 21:36 被阅读2624次

    第16章 Spring Boot安全集成Spring Security

    开发Web应用,对页面的安全控制通常是必须的。比如:对于没有访问权限的用户需要转到登录表单页面。要实现访问控制的方法多种多样,可以通过Aop、拦截器实现,也可以通过框架实现,例如:Apache Shiro、Spring Security。

    很多成熟的大公司都会有专门针对用户管理方面有一套完整的SSO(单点登录),ACL(权限访问控制),UC(用户中心)系统。 但是在我们开发中小型系统的时候,往往还是优先选择轻量级可用的业内通用的框架解决方案。

    Spring Security 就是一个Spring生态中关于安全方面的框架。它能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案。

    Spring Security,是一个基于Spring AOP和Servlet过滤器的安全框架。它提供全面的安全性解决方案,同时在Web请求级和方法调用级处理身份确认和授权。在Spring Framework基础上,Spring Security充分利用了依赖注入(DI,Dependency Injection)和面向切面技术。

    Spring Security提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC(Inversion of Control, 控制反转),DI和AOP(Aspect Oriented Progamming ,面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作,为基于J2EE企业应用软件提供了全面安全服务[0]。Spring Security的前身是 Acegi Security 。

    本章节使用SpringBoot集成Spring Security开发一个LightSword接口自动化测试平台,由浅入深的讲解SpringBoot集成Spring Security开发技术知识。

    本章节采用SpringBoot集成的主要的后端技术框架:

    编程语言:java,scala
    ORM框架:jpa
    View模板引擎:velocity
    安全框架:spring security
    数据库:mysql

    初阶 Security: 默认认证用户名密码

    项目pom.xml添加spring-boot-starter-security依赖

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
    

    重启你的应用。再次打开页面,你讲看到一个alert表单对话框:

    这个用户名,密码是什么呢?

    让我们来从SpringBoot源码寻找一下。

    你搜一下输出日志,会看到下面一段输出:

    2017-04-27 21:39:20.321  INFO 94124 --- [ost-startStop-1] b.a.s.AuthenticationManagerConfiguration : 
    
    Using default security password: 6c920ced-f1c1-4604-96f7-f0ce4e46f5d4
    

    这段日志是AuthenticationManagerConfiguration类里面的如下方法输出的:

            @Override
            public void configure(AuthenticationManagerBuilder auth) throws Exception {
                if (auth.isConfigured()) {
                    return;
                }
                User user = this.securityProperties.getUser();
                if (user.isDefaultPassword()) {
                    logger.info(String.format("%n%nUsing default security password: %s%n",
                            user.getPassword()));
                }
                Set<String> roles = new LinkedHashSet<>(user.getRole());
                withUser(user.getName()).password(user.getPassword())
                        .roles(roles.toArray(new String[roles.size()]));
                setField(auth, "defaultUserDetailsService", getUserDetailsService());
                super.configure(auth);
            }
    

    我们可以看出,是SecurityProperties这个Bean管理了用户名和密码。
    在SecurityProperties里面的一个内部静态类User类里面,管理了默认的认证的用户名与密码。代码如下

        public static class User {
    
            /**
             * Default user name.
             */
            private String name = "user";
    
            /**
             * Password for the default user name.
             */
            private String password = UUID.randomUUID().toString();
    
            /**
             * Granted roles for the default user name.
             */
            private List<String> role = new ArrayList<>(Collections.singletonList("USER"));
    
            private boolean defaultPassword = true;
    
            public String getName() {
                return this.name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            public String getPassword() {
                return this.password;
            }
    
            public void setPassword(String password) {
                if (password.startsWith("${") && password.endsWith("}")
                        || !StringUtils.hasLength(password)) {
                    return;
                }
                this.defaultPassword = false;
                this.password = password;
            }
    
            public List<String> getRole() {
                return this.role;
            }
    
            public void setRole(List<String> role) {
                this.role = new ArrayList<>(role);
            }
    
            public boolean isDefaultPassword() {
                return this.defaultPassword;
            }
    
        }
    

    综上所述,security默认的用户名是user, 默认密码是应用启动的时候,通过UUID算法随机生成的。默认的role是"USER"。

    当然,如果我们想简单改一下这个用户名密码,可以在application.properties配置你的用户名密码,例如

    # security
    security.user.name=admin
    security.user.password=admin
    

    当然这只是一个初级的配置,更复杂的配置,可以分不用角色,在控制范围上,能够拦截到方法级别的权限控制。 且看下文分解。

    中阶 Security:内存用户名密码认证

    在上面章节,我们什么都没做,就添加了spring-boot-starter-security依赖,整个应用就有了默认的认证安全机制。下面,我们来定制用户名密码。

    写一个extends WebSecurityConfigurerAdapter的配置类:

    package com.springboot.in.action.security;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    
    /**
     * Created by jack on 2017/4/27.
     */
    
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            super.configure(http);
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth
                .inMemoryAuthentication()
                .withUser("root")
                .password("root")
                .roles("USER");
        }
    
    }
    
    

    简要说明:

    1.通过 @EnableWebSecurity注解开启Spring Security的功能。使用@EnableGlobalMethodSecurity(prePostEnabled = true)这个注解,可以开启security的注解,我们可以在需要控制权限的方法上面使用@PreAuthorize,@PreFilter这些注解。

    2.extends 继承 WebSecurityConfigurerAdapter 类,并重写它的方法来设置一些web安全的细节。我们结合@EnableWebSecurity注解和继承WebSecurityConfigurerAdapter,来给我们的系统加上基于web的安全机制。

    3.在configure(HttpSecurity http)方法里面,默认的认证代码是:

            http
                .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                .formLogin().and()
                .httpBasic();
    

    从方法名我们基本可以看懂这些方法的功能。上面的那个默认的登录页面,就是SpringBoot默认的用户名密码认证的login页面。其源代码如下:

    
    <html><head><title>Login Page</title></head><body onload='document.f.username.focus();'>
    <h3>Login with Username and Password</h3><form name='f' action='/login' method='POST'>
    <table>
        <tr><td>User:</td><td><input type='text' name='username' value=''></td></tr>
        <tr><td>Password:</td><td><input type='password' name='password'/></td></tr>
        <tr><td colspan='2'><input name="submit" type="submit" value="Login"/></td></tr>
        <input name="_csrf" type="hidden" value="b2155184-80cf-48a2-b547-91bbe364c98e" />
    </table>
    </form></body></html>
    
    

    我们使用SpringBoot默认的配置super.configure(http),它通过 authorizeRequests() 定义哪些URL需要被保护、哪些不需要被保护。默认配置是所有访问页面都需要认证,才可以访问。

    4.通过 formLogin() 定义当需要用户登录时候,转到的登录页面。

    5.configureGlobal(AuthenticationManagerBuilder auth) 方法,在内存中创建了一个用户,该用户的名称为root,密码为root,用户角色为USER。

    我们再次启动应用,访问 http://localhost:8888
    页面自动跳转到: http://localhost:8888/login
    如下图所示:

    这个默认的登录页面是怎么冒出来的呢?是的,SpringBoot内置的,SpringBoot甚至给我们做好了一个极简的登录页面。这个登录页面是通过Filter实现的。具体的实现类是org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter。同时,这个DefaultLoginPageGeneratingFilter也是SpringBoot的默认内置的Filter。

    输入用户名,密码,点击Login

    成功跳转我们之前要访问的页面:

    不过,我们发现,SpringBoot应用的启动日志还是打印了如下一段:

    2017-04-27 22:51:44.059  INFO 95039 --- [ost-startStop-1] b.a.s.AuthenticationManagerConfiguration : 
    
    Using default security password: 5fadfb54-2096-4a0b-ad46-2dad3220c825
    

    但实际上,已经使用了我们定制的用户名密码了。

    如果我们要配置多个用户,多个角色,可参考使用如下示例的代码:

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth
                .inMemoryAuthentication()
                    .withUser("root")
                    .password("root")
                    .roles("USER")
                .and()
                    .withUser("admin").password("admin")
                    .roles("ADMIN", "USER")
                .and()
                    .withUser("user").password("user")
                    .roles("USER");
        }
    

    角色权限控制

    当我们的系统功能模块当需求发展到一定程度时,会不同的用户,不同角色使用我们的系统。这样就要求我们的系统可以做到,能够对不同的系统功能模块,开放给对应的拥有其访问权限的用户使用。

    Spring Security提供了Spring EL表达式,允许我们在定义URL路径访问(@RequestMapping)的方法上面添加注解,来控制访问权限。

    在标注访问权限时,根据对应的表达式返回结果,控制访问权限:

    true,表示有权限
    fasle,表示无权限

    Spring Security可用表达式对象的基类是SecurityExpressionRoot。

    
    package org.springframework.security.access.expression;
    
    import java.io.Serializable;
    import java.util.Collection;
    import java.util.HashSet;
    import java.util.Set;
    
    import org.springframework.security.access.PermissionEvaluator;
    import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
    import org.springframework.security.authentication.AuthenticationTrustResolver;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.AuthorityUtils;
    
    /**
     * Base root object for use in Spring Security expression evaluations.
     *
     * @author Luke Taylor
     * @since 3.0
     */
    public abstract class SecurityExpressionRoot implements SecurityExpressionOperations {
        protected final Authentication authentication;
        private AuthenticationTrustResolver trustResolver;
        private RoleHierarchy roleHierarchy;
        private Set<String> roles;
        private String defaultRolePrefix = "ROLE_";
    
        /** Allows "permitAll" expression */
        public final boolean permitAll = true;
    
        /** Allows "denyAll" expression */
        public final boolean denyAll = false;
        private PermissionEvaluator permissionEvaluator;
        public final String read = "read";
        public final String write = "write";
        public final String create = "create";
        public final String delete = "delete";
        public final String admin = "administration";
    
        /**
         * Creates a new instance
         * @param authentication the {@link Authentication} to use. Cannot be null.
         */
        public SecurityExpressionRoot(Authentication authentication) {
            if (authentication == null) {
                throw new IllegalArgumentException("Authentication object cannot be null");
            }
            this.authentication = authentication;
        }
    
        public final boolean hasAuthority(String authority) {
            return hasAnyAuthority(authority);
        }
    
        public final boolean hasAnyAuthority(String... authorities) {
            return hasAnyAuthorityName(null, authorities);
        }
    
        public final boolean hasRole(String role) {
            return hasAnyRole(role);
        }
    
        public final boolean hasAnyRole(String... roles) {
            return hasAnyAuthorityName(defaultRolePrefix, roles);
        }
    
        private boolean hasAnyAuthorityName(String prefix, String... roles) {
            Set<String> roleSet = getAuthoritySet();
    
            for (String role : roles) {
                String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
                if (roleSet.contains(defaultedRole)) {
                    return true;
                }
            }
    
            return false;
        }
    
        public final Authentication getAuthentication() {
            return authentication;
        }
    
        public final boolean permitAll() {
            return true;
        }
    
        public final boolean denyAll() {
            return false;
        }
    
        public final boolean isAnonymous() {
            return trustResolver.isAnonymous(authentication);
        }
    
        public final boolean isAuthenticated() {
            return !isAnonymous();
        }
    
        public final boolean isRememberMe() {
            return trustResolver.isRememberMe(authentication);
        }
    
        public final boolean isFullyAuthenticated() {
            return !trustResolver.isAnonymous(authentication)
                    && !trustResolver.isRememberMe(authentication);
        }
    
        /**
         * Convenience method to access {@link Authentication#getPrincipal()} from
         * {@link #getAuthentication()}
         * @return
         */
        public Object getPrincipal() {
            return authentication.getPrincipal();
        }
    
        public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
            this.trustResolver = trustResolver;
        }
    
        public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
            this.roleHierarchy = roleHierarchy;
        }
    
        /**
         * <p>
         * Sets the default prefix to be added to {@link #hasAnyRole(String...)} or
         * {@link #hasRole(String)}. For example, if hasRole("ADMIN") or hasRole("ROLE_ADMIN")
         * is passed in, then the role ROLE_ADMIN will be used when the defaultRolePrefix is
         * "ROLE_" (default).
         * </p>
         *
         * <p>
         * If null or empty, then no default role prefix is used.
         * </p>
         *
         * @param defaultRolePrefix the default prefix to add to roles. Default "ROLE_".
         */
        public void setDefaultRolePrefix(String defaultRolePrefix) {
            this.defaultRolePrefix = defaultRolePrefix;
        }
    
        private Set<String> getAuthoritySet() {
            if (roles == null) {
                roles = new HashSet<String>();
                Collection<? extends GrantedAuthority> userAuthorities = authentication
                        .getAuthorities();
    
                if (roleHierarchy != null) {
                    userAuthorities = roleHierarchy
                            .getReachableGrantedAuthorities(userAuthorities);
                }
    
                roles = AuthorityUtils.authorityListToSet(userAuthorities);
            }
    
            return roles;
        }
    
        public boolean hasPermission(Object target, Object permission) {
            return permissionEvaluator.hasPermission(authentication, target, permission);
        }
    
        public boolean hasPermission(Object targetId, String targetType, Object permission) {
            return permissionEvaluator.hasPermission(authentication, (Serializable) targetId,
                    targetType, permission);
        }
    
        public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
            this.permissionEvaluator = permissionEvaluator;
        }
    
        /**
         * Prefixes role with defaultRolePrefix if defaultRolePrefix is non-null and if role
         * does not already start with defaultRolePrefix.
         *
         * @param defaultRolePrefix
         * @param role
         * @return
         */
        private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String role) {
            if (role == null) {
                return role;
            }
            if (defaultRolePrefix == null || defaultRolePrefix.length() == 0) {
                return role;
            }
            if (role.startsWith(defaultRolePrefix)) {
                return role;
            }
            return defaultRolePrefix + role;
        }
    }
    
    
    

    通过阅读源码,我们可以更加深刻的理解其EL写法,并在写代码的时候正确的使用。变量defaultRolePrefix硬编码约定了role的前缀是"ROLE_"。

    同时,我们可以看出hasRole跟hasAnyRole是一样的。hasAnyRole是调用的hasAnyAuthorityName(defaultRolePrefix, roles)。所以,我们在学习一个框架或者一门技术的时候,最准确的就是源码。通过源码,我们可以更好更深入的理解技术的本质。

    SecurityExpressionRoot为我们提供的使用Spring EL表达式总结如下[1]:

    表达式 描述
    hasRole([role]) 当前用户是否拥有指定角色。
    hasAnyRole([role1,role2]) 多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回true。
    hasAuthority([auth]) 等同于hasRole
    hasAnyAuthority([auth1,auth2]) 等同于hasAnyRole
    Principle 代表当前用户的principle对象
    authentication 直接从SecurityContext获取的当前Authentication对象
    permitAll 总是返回true,表示允许所有的
    denyAll 总是返回false,表示拒绝所有的
    isAnonymous() 当前用户是否是一个匿名用户
    isRememberMe() 表示当前用户是否是通过Remember-Me自动登录的
    isAuthenticated() 表示当前用户是否已经登录认证成功了。
    isFullyAuthenticated() 如果当前用户既不是一个匿名用户,同时又不是通过Remember-Me自动登录的,则返回true。

    比如说,在lightsword系统中,我们设置测试报告页面,只针对ADMIN权限开放,代码如下:

    package com.springboot.in.action.controller
    
    import java.util
    
    import com.alibaba.fastjson.serializer.SerializerFeature
    import com.springboot.in.action.dao.HttpReportDao
    import org.springframework.beans.factory.annotation.Autowired
    import org.springframework.security.access.prepost.PreAuthorize
    import org.springframework.ui.Model
    import org.springframework.web.bind.annotation.{RequestMapping, RestController}
    import org.springframework.web.servlet.ModelAndView
    
    import scala.collection.JavaConversions._
    
    @RestController
    @RequestMapping(Array("/httpreport"))
    class HttpReportController @Autowired()(val HttpReportDao: HttpReportDao) {
    
      @RequestMapping(value = {
        Array("", "/")
      })
      @PreAuthorize("hasRole('ADMIN')") // Spring Security默认的角色前缀是”ROLE_”,使用hasRole方法时已经默认加上了
      def list(model: Model) = {
        val reports = HttpReportDao.findAll
        model.addAttribute("reports", reports)
    
        val rateList = new util.ArrayList[Double]
    
        val trendList = new util.ArrayList[Object]
    
        for (r <- reports) {
          rateList.add(r.rate)
    
          // QualityTrend
          val qt = new util.HashMap[String, Any]
    
          qt.put("id", r.id)
          qt.put("failed", r.fail)
          qt.put("totalCases", r.pass + r.fail)
          qt.put("rate", r.rate)
          trendList.add(qt)
        }
    
        val jsonstr = com.alibaba.fastjson.JSON.toJSONString(trendList, SerializerFeature.BrowserCompatible)
        println(jsonstr)
    
        model.addAttribute("rateList", rateList)
        model.addAttribute("trendList", jsonstr)
    
        new ModelAndView("/httpreport/list")
      }
    
    }
    
    

    然后,我们配置用户user为USER权限:

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth
                .inMemoryAuthentication()
                .withUser("root")
                .password("root")
                .roles("ADMIN", "USER")
                .and()
                .withUser("admin").password("admin")
                .roles("ADMIN", "USER")
                .and()
                .withUser("user").password("user")
                .roles("USER");
        }
    

    重启应用,用用户名:user,密码:user登录系统,访问/httpreport页面,我们将会看到如下,不允许访问的报错页面:

    简要说明

    在方法上添加@PreAuthorize这个注解,value="hasRole('ADMIN')")是Spring-EL expression,当表达式值为true,标识这个方法可以被调用。如果表达式值是false,标识此方法无权限访问。

    本小节完整的工程代码:
    https://github.com/EasySpringBoot/lightsword/tree/spring_security_with_in_memory_auth

    在Spring Security里面获取当前登录认证通过的用户信息

    如果我们想要在前端页面显示当前登录的用户怎么办呢?在在Spring Security里面怎样获取当前登录认证通过的用户信息?下面我们就来探讨这个问题。

    很好办。我们添加一个LoginFilter,默认拦截所有请求,把当前登录的用户放到系统session中即可。在Spring Security中,用户信息保存在SecurityContextHolder中。Spring Security使用一个Authentication对象来持有所有系统的安全认证相关的信息。这个信息的内容格式如下:

    {
        "accountNonExpired":true,
        "accountNonLocked":true,
        "authorities":[{
            "authority":"ROLE_ADMIN"
        },{
            "authority":"ROLE_USER"
        }],
        "credentialsNonExpired":true,
        "enabled":true,
        "username":"root"
    }
    

    这个Authentication对象信息其实就是User实体的信息(当然,密码没放进来)。

    public class User implements UserDetails, CredentialsContainer {
        private String password;
        private final String username;
        private final Set<GrantedAuthority> authorities;
        private final boolean accountNonExpired;
        private final boolean accountNonLocked;
        private final boolean credentialsNonExpired;
        private final boolean enabled;
            ....
    }
    

    我们可以使用下面的代码(Java)获得当前身份验证的用户的名称:

    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    
    if (principal instanceof UserDetails) {
        String username = ((UserDetails)principal).getUsername();
    } else {
        String username = principal.toString();
    }
    

    通过调用getContext()返回的对象是SecurityContext的实例对象,该实例对象保存在ThreadLocal线程本地存储中。使用Spring Security框架,通常的认证机制都是返回UserDetails实例。

    Spring MVC的 Web开发使用 Controller 基本上可以完成大部分需求,但是我们还可能会用到 Servlet、Filter、Listener、Interceptor 等等。

    在Spring Boot中添加自己的Servlet有两种方法,代码注册Servlet和注解自动注册(Filter和Listener也是如此)。

    (1)代码注册通过ServletRegistrationBean、 FilterRegistrationBean 和 ServletListenerRegistrationBean 获得控制。
    也可以通过实现 ServletContextInitializer 接口直接注册。使用代码注册Servlet(就不需要@ServletComponentScan注解)

    (2)在 SpringBootApplication 上使用@ServletComponentScan 注解后,Servlet、Filter、Listener 可以直接通过 @WebServlet、@WebFilter、@WebListener 注解自动注册。

    下面我们就采用第(2)种方法,通过添加一个LoginFilter,拦截所有请求,把当前登录信息放到系统session中,并在前端页面显示。

    1.添加一个实现了javax.servlet.Filter的LoginFilter,把当前登录信息放到系统session中

    代码如下

    package com.springboot.in.action.filter
    
    import javax.servlet._
    import javax.servlet.annotation.WebFilter
    import javax.servlet.http.HttpServletRequest
    
    import com.alibaba.fastjson.JSON
    import com.alibaba.fastjson.serializer.SerializerFeature
    import org.springframework.core.annotation.Order
    
    /**
      * Created by jack on 2017/4/28.
      */
    @Order(1) //@Order注解表示执行过滤顺序,值越小,越先执行
    @WebFilter(filterName = "loginFilter", urlPatterns = Array("/*")) //需要在spring-boot的入口处加注解@ServletComponentScan, 如果不指定,默认url-pattern是/*
    class LoginFilter extends Filter {
      override def init(filterConfig: FilterConfig): Unit = {}
    
      override def destroy(): Unit = {}
    
      override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = {
        val session = request.asInstanceOf[HttpServletRequest].getSession
    
        import org.springframework.security.core.context.SecurityContextHolder
        import org.springframework.security.core.userdetails.UserDetails
    
        val principal = SecurityContextHolder.getContext.getAuthentication.getPrincipal
    
    
        println("LoginFilter:" + JSON.toJSONString(principal, SerializerFeature.PrettyFormat))
    
        var username = ""
        if (principal.isInstanceOf[UserDetails]) {
          username = principal.asInstanceOf[UserDetails].getUsername
        }
        else {
          username = principal.toString
        }
        session.setAttribute("username", username)
    
        chain.doFilter(request, response)
      }
    }
    
    

    我们通过

    val principal = SecurityContextHolder.getContext.getAuthentication.getPrincipal
    
    
    if (principal.isInstanceOf[UserDetails]) {
          username = principal.asInstanceOf[UserDetails].getUsername
        }
        else {
          username = principal.toString
        }
    
    

    拿到认证信息,然后把用户名放到session中:

    session.setAttribute("username", username)
    chain.doFilter(request, response)
    
    

    其中,@WebFilter(filterName = "loginFilter", urlPatterns = Array("/")) ,这个注解用来声明一个Servlet的Filter,这个加注解@WebFilter的LoginFilter类必须要实现javax.servlet.Filter接口。它会在容器部署的时候扫描处理。如果不指定urlPatterns,默认url-pattern是/。这个@WebFilter注解,在SpringBoot中,要给启动类加上注解@ServletComponentScan,开启扫描Servlet组件功能。

    2.给启动类加上注解@ServletComponentScan

    package com.springboot.in.action
    
    import org.springframework.boot.autoconfigure.SpringBootApplication
    import org.springframework.boot.web.servlet.ServletComponentScan
    
    @SpringBootApplication
    @ServletComponentScan(basePackages = Array("com.springboot.in.action"))
    class AppConfig
    
    
    

    这个注解将开启扫描Servlet组件功能。那些被标注了@WebFilter,@WebServlet,@WebListener的Bean将会注册到容器中。需要注意的一点是,这个扫描动作只在当我们使用的是嵌入式Servlet容器的时候才起作用。完成Bean注册工作的类是org.springframework.boot.web.servlet.ServletComponentScanRegistrar,它实现了Spring的ImportBeanDefinitionRegistrar接口。

    3.前端显示用户信息

    Velocity内置了一些对象,例如:$request、$response、$session,这些对象可以在vm模版里可以直接调用。所以我们只需要使用$session取出,当初我们放进session的对应key的属性值即可。

    我们在LoginFilter里面是这样放进去的:

    session.setAttribute("username", username)
    

    在前端页面直接这样取出username

    <div class="pull-left info">
        <p>$session.getAttribute('username')</p>
        <a href="#"><i class="fa fa-circle text-success"></i> Online</a>
    </div>
    

    4.运行测试

    部署应用运行,我们看一下运行日志:

    2017-04-28 21:42:46.072  INFO 2961 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8888 (http)
    2017-04-28 21:42:46.097  INFO 2961 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
    2017-04-28 21:42:46.099  INFO 2961 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.0.33
    2017-04-28 21:42:46.328  INFO 2961 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
    2017-04-28 21:42:46.328  INFO 2961 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 4325 ms
    2017-04-28 21:42:46.984  INFO 2961 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'characterEncodingFilter' to: [/*]
    2017-04-28 21:42:46.984  INFO 2961 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
    2017-04-28 21:42:46.985  INFO 2961 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'httpPutFormContentFilter' to: [/*]
    2017-04-28 21:42:46.985  INFO 2961 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'requestContextFilter' to: [/*]
    2017-04-28 21:42:46.987  INFO 2961 --- [ost-startStop-1] .e.DelegatingFilterProxyRegistrationBean : Mapping filter: 'springSecurityFilterChain' to: [/*]
    2017-04-28 21:42:46.988  INFO 2961 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'com.springboot.in.action.filter.LoginFilter' to: [/*]
    2017-04-28 21:42:46.988  INFO 2961 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
    2017-04-28 21:42:47.734  INFO 2961 --- [ost-startStop-1] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@7a20e3e2, org.springframework.security.web.context.SecurityContextPersistenceFilter@6d522c58, org.springframework.security.web.header.HeaderWriterFilter@43ba5fdb, org.springframework.security.web.csrf.CsrfFilter@4ae04f7a, org.springframework.security.web.authentication.logout.LogoutFilter@31e3441f, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@30dfa22c, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@605f9361, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@22a03a5f, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7806751c, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@67831f83, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@134ae8c4, org.springframework.security.web.session.SessionManagementFilter@4c60d4b8, org.springframework.security.web.access.ExceptionTranslationFilter@2be01c38, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@34cb7b6d]
    2017-04-28 21:42:48.105  INFO 2961 --- [           main] j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistence unit 'default'
    2017-04-28 21:42:48.121  INFO 2961 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [
    

    在上面的日志里面,我们可以看到如下一行

    o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'com.springboot.in.action.filter.LoginFilter' to: [/*]
    

    这表明我们定义的LoginFilter类成功注册,路径映射到/*。同时,我们在

    o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: 
    

    这行日志后面,看到SpringBoot默认创建了的那些Filter Chain。这些Filter如下:

    
    
    org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@7a20e3e2, 
    
    org.springframework.security.web.context.SecurityContextPersistenceFilter@6d522c58,
    
    org.springframework.security.web.header.HeaderWriterFilter@43ba5fdb, 
    
    org.springframework.security.web.csrf.CsrfFilter@4ae04f7a, 
    
    org.springframework.security.web.authentication.logout.LogoutFilter@31e3441f,
    
     org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@30dfa22c, 
    
    org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@605f9361, 
    
    org.springframework.security.web.authentication.www.BasicAuthenticationFilter@22a03a5f, 
    
    org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7806751c, 
    
    org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@67831f83, 
    
    org.springframework.security.web.authentication.AnonymousAuthenticationFilter@134ae8c4, 
    
    org.springframework.security.web.session.SessionManagementFilter@4c60d4b8, 
    
    org.springframework.security.web.access.ExceptionTranslationFilter@2be01c38, 
    
    org.springframework.security.web.access.intercept.FilterSecurityInterceptor@34cb7b6d
    
    
    

    SpringBoot在背后,为我们默默做了这么多事情。

    好了,言归正传,我们使用root用户名登录,我们可以看到页面上正确展示了我们当前登录的用户,如下图

    SpringBoot注册Servlet、Filter、Listener的方法

    我们刚才是使用@WebFilter注解一个javax.servlet.Filter的实现类来实现一个LoginFilter。

    基于JavaConfig,SpringBoot同样可以使用如下的方式实现Servlet、Filter、Listener的Bean的配置:

    @Configuration
    public class WebConfig {
    
    
        @Bean
        public ServletRegistrationBean servletRegistrationBean_demo2(){
            ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
            servletRegistrationBean.addUrlMappings("/demo-servlet");
            servletRegistrationBean.setServlet(new DemoServlet());
            return servletRegistrationBean;
        }
    
        @Bean
        public FilterRegistrationBean filterRegistrationBean(){
    
            FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
            filterRegistrationBean.setFilter(new LoginFilter());
            Set<String> set = new HashSet<String>();
            set.add("/*");
            filterRegistrationBean.setUrlPatterns(set);
            return filterRegistrationBean;
        }
    
        @Bean
        public ServletListenerRegistrationBean servletListenerRegistrationBean(){
            ServletListenerRegistrationBean servletListenerRegistrationBean =  new ServletListenerRegistrationBean();
            servletListenerRegistrationBean.setListener(new Log4jConfigListener());
            servletListenerRegistrationBean.addInitParameter("log4jConfigLocation","classpath:log4j.properties");
            return servletListenerRegistrationBean;
        }
    }
    
    

    从这里我们可以看出,JavaConfig在SpringBoot的自动配置中实现Bean注册的基本使用方式。

    进阶 Security: 用数据库存储用户和角色,实现安全认证

    本节我们将在我们之前的系统上,实现一个用数据库存储用户和角色,实现系统的安全认证。在权限角色上,我们简单设计两个用户角色:USER,ADMIN。

    我们设计页面的权限如下:

    首页/ : 所有人可访问
    登录页 /login: 所有人可访问
    普通用户权限页 /httpapi, /httpsuite: 登录后的用户都可访问
    管理员权限页 /httpreport : 仅管理员可访问
    无权限提醒页: 当一个用户访问了其没有权限的页面,我们使用全局统一的异常处理页面提示。

    1.数据库层设计:新建三张表User,Role,UserRole

    对应的领域实体模型类如下:

    用户表

    package com.springboot.in.action.entity
    
    import javax.persistence.{Entity, GeneratedValue, GenerationType, Id}
    
    import scala.beans.BeanProperty
    
    /**
      * Created by jack on 2017/4/29.
      */
    @Entity
    class User {
    
      @Id
      @GeneratedValue(strategy = GenerationType.AUTO)
      @BeanProperty
      var id: Integer = _
      @BeanProperty
      var userName: String = _
      @BeanProperty
      var password: String = _
    
    }
    
    

    角色表

    package com.springboot.in.action.entity
    
    import javax.persistence.{Entity, GeneratedValue, GenerationType, Id}
    
    import scala.beans.BeanProperty
    
    /**
      * Created by jack on 2017/4/29.
      */
    @Entity
    class Role {
    
      @Id
      @GeneratedValue(strategy = GenerationType.AUTO)
      @BeanProperty
      var id: Integer = _
      @BeanProperty
      var role: String = _
    
    }
    

    用户角色关联表

    package com.springboot.in.action.entity
    
    import javax.persistence.{Entity, GeneratedValue, GenerationType, Id}
    
    import scala.beans.BeanProperty
    
    /**
      * Created by jack on 2017/4/29.
      */
    @Entity
    class UserRole {
      @Id
      @GeneratedValue(strategy = GenerationType.AUTO)
      @BeanProperty
      var id: Integer = _
      @BeanProperty
      var userId: Integer = _
      @BeanProperty
      var roleId: Integer = _
    
    
    }
    
    
    

    为了方便测试,我们后面会写一个用户测试数据的自动生成的Bean,用来做测试数据的自动初始化工作。

    2.配置Spring Security

    我们首先使用Spring Security帮我们做登录、登出的处理,以及当用户未登录时只能访问: http://localhost:8888/ 以及 http://localhost:8888/login 两个页面。

    同样的,我们要写一个继承WebSecurityConfigurerAdapter的配置类:

    package com.springboot.in.action.security;
    
    import com.springboot.in.action.service.LightSwordUserDetailService;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    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;
    
    /**
     * Created by jack on 2017/4/27.
     */
    
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
    //使用@EnableGlobalMethodSecurity(prePostEnabled = true)
    // 这个注解,可以开启security的注解,我们可以在需要控制权限的方法上面使用@PreAuthorize,@PreFilter这些注解。
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        //@Autowired
        //LightSwordUserDetailService lightSwordUserDetailService;
    
        @Override
        @Bean
        public UserDetailsService userDetailsService() { //覆盖写userDetailsService方法 (1)
            return new LightSwordUserDetailService();
    
        }
    
        /**
         * If subclassed this will potentially override subclass configure(HttpSecurity)
         *
         * @param http
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //super.configure(http);
            http.csrf().disable();
    
            http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/amchart/**",
                    "/bootstrap/**",
                    "/build/**",
                    "/css/**",
                    "/dist/**",
                    "/documentation/**",
                    "/fonts/**",
                    "/js/**",
                    "/pages/**",
                    "/plugins/**"
                ).permitAll() //默认不拦截静态资源的url pattern (2)
                .anyRequest().authenticated().and()
                .formLogin().loginPage("/login")// 登录url请求路径 (3)
                .defaultSuccessUrl("/httpapi").permitAll().and() // 登录成功跳转路径url(4)
                .logout().permitAll();
    
            http.logout().logoutSuccessUrl("/"); // 退出默认跳转页面 (5)
    
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //auth
            //    .inMemoryAuthentication()
            //    .withUser("root")
            //    .password("root")
            //    .roles("ADMIN", "USER")
            //    .and()
            //    .withUser("admin").password("admin")
            //    .roles("ADMIN", "USER")
            //    .and()
            //    .withUser("user").password("user")
            //    .roles("USER");
    
            //AuthenticationManager使用我们的 lightSwordUserDetailService 来获取用户信息
            auth.userDetailsService(userDetailsService()); // (6)
        }
    
    }
    
    

    这里只做了基本的配置,其中:

    (1)覆盖写userDetailsService方法,具体的LightSwordUserDetailService实现类,我们下面紧接着会讲。

    (2)默认不拦截静态资源的url pattern。我们也可以用下面的WebSecurity这个方式跳过静态资源的认证

    public void configure(WebSecurity web) throws Exception {
        web
            .ignoring()
            .antMatchers("/resourcesDir/**");
    }
    

    (3)跳转登录页面url请求路径为/login,我们需要定义一个Controller把路径映射到login.html。代码如下

    package com.springboot.in.action.security
    
    import org.springframework.context.annotation.Configuration
    import org.springframework.web.servlet.config.annotation.{ViewControllerRegistry, WebMvcConfigurerAdapter}
    
    
    /**
      * Created by jack on 2017/4/30.
      */
    @Configuration
    class WebMvcConfig extends WebMvcConfigurerAdapter {
      /**
        * 统一注册纯RequestMapping跳转View的Controller
        */
      override def addViewControllers(registry: ViewControllerRegistry) {
        registry.addViewController("/login").setViewName("/login")
      }
    }
    

    这里我们直接采用ViewControllerRegistry来注册一个纯路径映射的Controller方法。

    login.html

    #parse("/common/header.html")
    
    <div class="container-fluid">
    
        <div class="box box-success">
            <div class="box-header">
                <h2>LightSword自动化测试平台(<a href="http://localhost:8888/">LightSword</a>)</h2>
            </div>
    
            <div class="box-body">
    
                <h3>登录</h3>
                <form name='f' action='/login' method='POST'>
                    <table>
                        <tr>
                            <td>用户名:</td>
                            <td><input type='text' name='username' value=''></td>
                        </tr>
                        <tr>
                            <td>密码:</td>
                            <td><input type='password' name='password'/></td>
                        </tr>
                        <tr>
                            <td colspan='2'><input name="submit" type="submit" value="登录"/></td>
                        </tr>
                        <!--<input name="_csrf" type="hidden" value="${_csrf}"/>-->
    
                    </table>
                </form>
            </div>
        </div>
    </div>
    
    <script>
        $(function () {
            $('[name=f]').focus()
        })
    </script>
    
    #parse("/common/footer.html")
    
    

    (4)登录成功后跳转的路径为/httpapi
    (5)退出后跳转到的url为/
    (6)认证鉴权信息的Bean,采用我们自定义的从数据库中获取用户信息的LightSwordUserDetailService类。

    我们同样使用@EnableGlobalMethodSecurity(prePostEnabled = true)这个注解,开启security的注解,这样我们可以在需要控制权限的方法上面使用@PreAuthorize,@PreFilter这些注解。

    3.自定义LightSwordUserDetailService

    从数据库中获取用户信息的操作是必不可少的,我们首先来实现UserDetailsService,这个接口需要我们实现一个方法:loadUserByUsername。即从数据库中取出用户名、密码以及权限相关的信息。最后返回一个UserDetails 实现类。

    代码如下:

    package com.springboot.in.action.service
    
    import java.util
    
    import com.springboot.in.action.dao.{RoleDao, UserDao, UserRoleDao}
    import org.springframework.beans.factory.annotation.Autowired
    import org.springframework.security.core.authority.SimpleGrantedAuthority
    import org.springframework.security.core.userdetails.{User, UserDetails, UserDetailsService, UsernameNotFoundException}
    import org.springframework.stereotype.Service
    import org.springframework.util.StringUtils
    
    
    /**
      * Created by jack on 2017/4/29.
      */
    @Service
    class LightSwordUserDetailService extends UserDetailsService {
    
      @Autowired var userRoleDao: UserRoleDao = _
      @Autowired var userDao: UserDao = _
      @Autowired var roleDao: RoleDao = _
    
    
      override def loadUserByUsername(username: String): UserDetails = {
    
    //    val user = userDao.findByUsername(username) // 直接调用jpa自动生成的方法
        val user = userDao.getUserByUsername(username)
        if (user == null) throw new UsernameNotFoundException(username + " not found")
    
        val authorities = new util.ArrayList[SimpleGrantedAuthority]
        val userRoles = userRoleDao.listByUserId(user.id)
    
        // Scala中调用java的collection类,使用scala的foreach,编译器会提示无法找到result的foreach方法。因为这里的userRoles的类型为java.util.List。若要将其转换为Scala的集合,就需要增加如下语句:
        import scala.collection.JavaConversions._
        for (userRole <- userRoles) {
          val roleId = userRole.roleId
          val roleName = roleDao.findOne(roleId).role
          if (!StringUtils.isEmpty(roleName)) {
            authorities.add(new SimpleGrantedAuthority(roleName))
          }
    
          System.err.println("username is " + username + ", " + roleName)
        }
    
        new User(username, user.password, authorities)
      }
    }
    
    

    4.用户退出

    我们在configure(HttpSecurity http)方法里面定义了任何权限都允许退出,

    *.logout().permitAll();
    http.logout().logoutSuccessUrl("/"); // 退出默认跳转页面 (4)
    

    SpringBoot集成Security的默认退出请求是/logout , 我们在顶部导航栏加个退出功能。代码如下

                        <li>
                            <a href="/logout">
                                <i class="fa fa-power-off"></i>
                            </a>
                        </li>
    

    5.配置错误处理页面

    访问发生错误时,跳转到系统统一异常处理页面。

    我们首先添加一个GlobalExceptionHandlerAdvice,使用@ControllerAdvice注解:

    package com.springboot.in.action.advice
    
    import org.springframework.web.bind.annotation.{ControllerAdvice, ExceptionHandler}
    import org.springframework.web.context.request.WebRequest
    import org.springframework.web.servlet.ModelAndView
    
    /**
      * Created by jack on 2017/4/27.
      */
    @ControllerAdvice
    class GlobalExceptionHandlerAdvice {
    
      @ExceptionHandler(value = Array(classOf[Exception])) //表示捕捉到所有的异常,你也可以捕捉一个你自定义的异常
      def exception(exception: Exception, request: WebRequest): ModelAndView = {
        val modelAndView = new ModelAndView("/error") //error页面
        modelAndView.addObject("errorMessage", exception.getMessage)
        modelAndView.addObject("stackTrace", exception.getStackTrace)
        modelAndView
      }
    
    }
    
    

    其中,@ExceptionHandler(value = Array(classOf[Exception])) ,表示捕捉到所有的异常,这里你也可以捕捉一个你自定义的异常。比如说,针对安全认证的Exception,我们可以单独定义处理。此处不再赘述。感兴趣的读者,可自行尝试。

    错误统一处理页面error.html

    #parse("/common/header.html")
    <h1>系统异常统一处理页面</h1>
    
    <h3>异常消息: $errorMessage</h3>
    
    <h3>异常堆栈信息:</h3>
    <code>
        #foreach($e in $stackTrace)
        $e
        #end
    </code>
    
    
    #parse("/common/footer.html")
    
    

    6.测试运行

    为了方便测试用户权限功能,我们给数据库初始化一些测试数据进去:

    package com.springboot.in.action.service
    
    import java.util.UUID
    import javax.annotation.PostConstruct
    
    import com.springboot.in.action.dao.{RoleDao, UserDao, UserRoleDao}
    import com.springboot.in.action.entity.{Role, User, UserRole}
    import org.springframework.beans.factory.annotation.Autowired
    import org.springframework.stereotype.Service
    
    /**
      * Created by jack on 2017/4/29.
      * 初始化测试数据
      */
    @Service // 需要初始化数据时,打开注释即可。
    class DataInit @Autowired()(val userDao: UserDao,
                                val userRoleDao: UserRoleDao,
                                val roleDao: RoleDao) {
    
      @PostConstruct def dataInit(): Unit = {
        val uuid = UUID.randomUUID().toString
    
        val admin = new User
        val jack = new User
    
        admin.username = "admin_" + uuid
        admin.password = "admin"
    
        jack.username = "jack_" + uuid
        jack.password = "123456"
    
        userDao.save(admin)
        userDao.save(jack)
    
        val adminRole = new Role
        val userRole = new Role
    
        adminRole.role = "ROLE_ADMIN"
        userRole.role = "ROLE_USER"
    
        roleDao.save(adminRole)
        roleDao.save(userRole)
    
        val userRoleAdminRecord1 = new UserRole
        userRoleAdminRecord1.userId = admin.id
        userRoleAdminRecord1.roleId = adminRole.id
        userRoleDao.save(userRoleAdminRecord1)
    
        val userRoleAdminRecord2 = new UserRole
        userRoleAdminRecord2.userId = admin.id
        userRoleAdminRecord2.roleId = userRole.id
        userRoleDao.save(userRoleAdminRecord2)
    
        val userRoleJackRecord = new UserRole
        userRoleJackRecord.userId = jack.id
        userRoleJackRecord.roleId = userRole.id
        userRoleDao.save(userRoleJackRecord)
    
    
      }
    
    }
    
    

    同样的,在我们需要权限控制的页面对应的方法上添加@PreAuthorize注解,value="hasRole('ADMIN')")或"hasRole('USER')"等。

    部署应用,访问http://localhost:8888/httpapi , 我们可以看到系统自动拦截跳转到登录页面

    输入USER角色的用户名jack,密码123456,系统跳转到默认登录成功页面。我们访问无权限页面http://localhost:8888/httpreport ,可以看出,系统拦截到无权限,跳转到了错误提示页面

    技术点讲解

    Spring Security 相关接口和类

    1. UserDetails 接口:作用是提供认证相关的用户的信息. 其主要的方法就是:String getPassword(); 和 String getUsername();

    2. User 类: 特指 org.springframework.security.core.userdetails 包中的 User 类。 它实现了 UserDetails 接口。

    3. UserDetailsService 接口:作用是在特定用户权限认证时,用于加载用户信息。 该接口只有一个方法,用于返回用户的信息:UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;那么,它的框架里面默认的实现类有 InMemoryUserDetailsManager,CachingUserDetailsService 和 JdbcDaoImpl,一个用于从内存中拿到用户信息,一个用于从数据库中拿到用户信息。

    我们自定义LightSwordUserDetailService实现了UserDetailsService接口,从我们自己定义的数据库表里面取得用户信息来认证鉴权。

    小结

    本章节通过一个简单而完整的示例完成了对Web应用的登录,权限等的安全控制。完整工程源代码:

    https://github.com/EasySpringBoot/lightsword/tree/spring_security_with_db_user_role_2017.4.28

    Spring Security提供的功能还远不止于此,更多Spring Security的使用可参见【参考资料】部分。

    参考资料:
    0.http://baike.baidu.com/item/spring%20security
    1.http://elim.iteye.com/blog/2247073
    2.http://blog.csdn.net/u012373815/article/details/54632176
    3.https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples/spring-boot-sample-secure
    4.http://www.open-open.com/lib/view/open1464482054012.html
    5.https://github.com/EasySpringBoot/spring-security
    6.http://docs.spring.io/spring-security/site/docs/4.1.0.RELEASE/reference/htmlsingle/#jc-authentication
    7.https://github.com/pzxwhc/MineKnowContainer/issues/58
    8.http://stackoverflow.com/questions/22998731/httpsecurity-websecurity-and-authenticationmanagerbuilder
    9.https://spring.io/blog/2013/07/03/spring-security-java-config-preview-web-security/
    10.https://springcloud.cc/spring-security-zhcn.html

    相关文章

      网友评论

      • spilledyear:前后端分离的情况下?需要在WenSecurity中方行所有的前端请求接口吗?
        编程界的小学生:可以解决你的问题
        通用的权限处理框架,基于Spring-Security的二次封装,可扩展,可配置。即使不想用的话也可以学习学习这种封装可扩展可配置框架的思想。
        https://gitee.com/geekerdream/common-security/tree/master
        给个star吧,不容易的。
      • 编程界的小学生:通用的权限处理框架,基于Spring-Security的二次封装,可扩展,可配置。即使不想用的话也可以学习学习这种封装可扩展可配置框架的思想。
        https://gitee.com/geekerdream/common-security/tree/master
        给个star吧,不容易的。
      • Winter_Chen:如果同时存在两个验证方式,一个是数据库账号密码,另一个是qq登录,那么两个验证方式如何存在“或”的关系呢?
        光剑书架上的书:@埃尔文放弃 :+1:
        Winter_Chen:@东海陈光剑 已经搞定了
        光剑书架上的书:@埃尔文放弃 有先后顺序吗?

      本文标题:《Spring Boot极简教程》第16章 Spring Boo

      本文链接:https://www.haomeiwen.com/subject/zldxzttx.html