Spring Boot Security 详解

简介

Spring Security,这是一种基于 Spring AOP 和 Servlet 过滤器的安全框架。它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权。

工作流程

从网上找了一张 Spring Security 的工作流程图,如下。

图中标记的 MyXXX,就是我们项目中需要配置的。

快速上手

建表

表结构

建表语句

DROP TABLE IF EXISTS `user`;
DROP TABLE IF EXISTS `role`;
DROP TABLE IF EXISTS `user_role`;
DROP TABLE IF EXISTS `role_permission`;
DROP TABLE IF EXISTS `permission`;

CREATE TABLE <span class="hljs-keyword">user</span> (
id bigint(11) NOT NULL AUTO_INCREMENT,
username varchar(255) NOT NULL,
password varchar(255) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE role (
id bigint(11) NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE user_role (
user_id bigint(11) NOT NULL,
role_id bigint(11) NOT NULL
);
CREATE TABLE role_permission (
role_id bigint(11) NOT NULL,
permission_id bigint(11) NOT NULL
);
CREATE TABLE permission (
id bigint(11) NOT NULL AUTO_INCREMENT,
url varchar(255) NOT NULL,
name varchar(255) NOT NULL,
description varchar(255) NULL,
pid bigint(11) NOT NULL,
PRIMARY KEY (id)
);

INSERT INTO user (id, username, password) VALUES (1,'user','e10adc3949ba59abbe56e057f20f883e');
INSERT INTO user (id, username , password) VALUES (2,'admin','e10adc3949ba59abbe56e057f20f883e');
INSERT INTO role (id, name) VALUES (1,'USER');
INSERT INTO role (id, name) VALUES (2,'ADMIN');
INSERT INTO permission (id, url, name, pid) VALUES (1,'/user/common','common',0);
INSERT INTO permission (id, url, name, pid) VALUES (2,'/user/admin','admin',0);
INSERT INTO user_role (user_id, role_id) VALUES (1, 1);
INSERT INTO user_role (user_id, role_id) VALUES (2, 1);
INSERT INTO user_role (user_id, role_id) VALUES (2, 2);
INSERT INTO role_permission (role_id, permission_id) VALUES (1, 1);
INSERT INTO role_permission (role_id, permission_id) VALUES (2, 1);
INSERT INTO role_permission (role_id, permission_id) VALUES (2, 2);

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-security4</artifactId>
</dependency>

application.yml

spring:
  thymeleaf:
    mode: HTML5
    encoding: UTF-8
    cache: false

datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/spring-security?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false
username: root
password: root

User

public class User implements UserDetails , Serializable {
<span class="hljs-keyword">private</span> Long id;
<span class="hljs-keyword">private</span> String username;
<span class="hljs-keyword">private</span> String password;

<span class="hljs-keyword">private</span> List&lt;Role&gt; authorities;

<span class="hljs-keyword">public</span> Long <span class="hljs-title function_">getId</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> id;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setId</span><span class="hljs-params">(Long id)</span> {
    <span class="hljs-built_in">this</span>.id = id;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">getUsername</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> username;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setUsername</span><span class="hljs-params">(String username)</span> {
    <span class="hljs-built_in">this</span>.username = username;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">getPassword</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> password;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setPassword</span><span class="hljs-params">(String password)</span> {
    <span class="hljs-built_in">this</span>.password = password;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> List&lt;Role&gt; <span class="hljs-title function_">getAuthorities</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> authorities;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setAuthorities</span><span class="hljs-params">(List&lt;Role&gt; authorities)</span> {
    <span class="hljs-built_in">this</span>.authorities = authorities;
}

<span class="hljs-comment">/**
 * 用户账号是否过期
 */</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isAccountNonExpired</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}

<span class="hljs-comment">/**
 * 用户账号是否被锁定
 */</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isAccountNonLocked</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}

<span class="hljs-comment">/**
 * 用户密码是否过期
 */</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isCredentialsNonExpired</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}

<span class="hljs-comment">/**
 * 用户是否可用
 */</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isEnabled</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}

}

上面的 User 类实现了 UserDetails 接口,该接口是实现 Spring Security 认证信息的核心接口。其中 getUsername 方法为 UserDetails 接口 的方法,这个方法返回 username,也可以是其他的用户信息,例如手机号、邮箱等。getAuthorities() 方法返回的是该用户设置的权限信息,在本实例中,从数据库取出用户的所有角色信息,权限信息也可以是用户的其他信息,不一定是角色信息。另外需要读取密码,最后几个方法一般情况下都返回 true,也可以根据自己的需求进行业务判断。

Role

public class Role implements GrantedAuthority {
<span class="hljs-keyword">private</span> Long id;
<span class="hljs-keyword">private</span> String name;

<span class="hljs-keyword">public</span> Long <span class="hljs-title function_">getId</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> id;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setId</span><span class="hljs-params">(Long id)</span> {
    <span class="hljs-built_in">this</span>.id = id;
}

<span class="hljs-keyword">public</span> String <span class="hljs-title function_">getName</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> name;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setName</span><span class="hljs-params">(String name)</span> {
    <span class="hljs-built_in">this</span>.name = name;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">getAuthority</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> name;
}

}

Role 类实现了 GrantedAuthority 接口,并重写 getAuthority() 方法。权限点可以为任何字符串,不一定非要用角色名。

所有的 Authentication 实现类都保存了一个 GrantedAuthority 列表,其表示用户所具有的权限。GrantedAuthority 是通过 AuthenticationManager 设置到 Authentication 对象中的,然后 AccessDecisionManager 将从 Authentication 中获取用户所具有的 GrantedAuthority 来鉴定用户是否具有访问对应资源的权限。

MyUserDetailsService

@Service
public class MyUserDetailsService implements UserDetailsService {
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> UserMapper userMapper;
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> RoleMapper roleMapper;

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> UserDetails <span class="hljs-title function_">loadUserByUsername</span><span class="hljs-params">(String userName)</span> <span class="hljs-keyword">throws</span> UsernameNotFoundException {
    <span class="hljs-comment">//查数据库</span>
    <span class="hljs-type">User</span> <span class="hljs-variable">user</span> <span class="hljs-operator">=</span> userMapper.loadUserByUsername( userName );
    <span class="hljs-keyword">if</span> (<span class="hljs-literal">null</span> != user) {
        List&lt;Role&gt; roles = roleMapper.getRolesByUserId( user.getId() );
        user.setAuthorities( roles );
    }

    <span class="hljs-keyword">return</span> user;
}

}

Service 层需要实现 UserDetailsService 接口,该接口是根据用户名获取该用户的所有信息, 包括用户信息和权限点。

MyInvocationSecurityMetadataSourceService

@Component
public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource {
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> PermissionMapper permissionMapper;

<span class="hljs-comment">/**
 * 每一个资源所需要的角色 Collection&lt;ConfigAttribute&gt;决策器会用到
 */</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> HashMap&lt;String, Collection&lt;ConfigAttribute&gt;&gt; map =<span class="hljs-literal">null</span>;


<span class="hljs-comment">/**
 * 返回请求的资源需要的角色
 */</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> Collection&lt;ConfigAttribute&gt; <span class="hljs-title function_">getAttributes</span><span class="hljs-params">(Object o)</span> <span class="hljs-keyword">throws</span> IllegalArgumentException {
    <span class="hljs-keyword">if</span> (<span class="hljs-literal">null</span> == map) {
        loadResourceDefine();
    }
    <span class="hljs-comment">//object 中包含用户请求的request 信息</span>
    <span class="hljs-type">HttpServletRequest</span> <span class="hljs-variable">request</span> <span class="hljs-operator">=</span> ((FilterInvocation) o).getHttpRequest();
    <span class="hljs-keyword">for</span> (Iterator&lt;String&gt; it = map.keySet().iterator() ; it.hasNext();) {
        <span class="hljs-type">String</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> it.next();
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">new</span> <span class="hljs-title class_">AntPathRequestMatcher</span>( url ).matches( request )) {
            <span class="hljs-keyword">return</span> map.get( url );
        }
    }

    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> Collection&lt;ConfigAttribute&gt; <span class="hljs-title function_">getAllConfigAttributes</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">supports</span><span class="hljs-params">(Class&lt;?&gt; aClass)</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}

<span class="hljs-comment">/**
 * 初始化 所有资源 对应的角色
 */</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">loadResourceDefine</span><span class="hljs-params">()</span> {
    map = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>&lt;&gt;(<span class="hljs-number">16</span>);
    <span class="hljs-comment">//权限资源 和 角色对应的表  也就是 角色权限 中间表</span>
    List&lt;RolePermisson&gt; rolePermissons = permissionMapper.getRolePermissions();

    <span class="hljs-comment">//某个资源 可以被哪些角色访问</span>
    <span class="hljs-keyword">for</span> (RolePermisson rolePermisson : rolePermissons) {

        <span class="hljs-type">String</span> <span class="hljs-variable">url</span> <span class="hljs-operator">=</span> rolePermisson.getUrl();
        <span class="hljs-type">String</span> <span class="hljs-variable">roleName</span> <span class="hljs-operator">=</span> rolePermisson.getRoleName();
        <span class="hljs-type">ConfigAttribute</span> <span class="hljs-variable">role</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">SecurityConfig</span>(roleName);

        <span class="hljs-keyword">if</span>(map.containsKey(url)){
            map.get(url).add(role);
        }<span class="hljs-keyword">else</span>{
            List&lt;ConfigAttribute&gt; list =  <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();
            list.add( role );
            map.put( url , list );
        }
    }
}

}

MyInvocationSecurityMetadataSourceService 类实现了 FilterInvocationSecurityMetadataSource,FilterInvocationSecurityMetadataSource 的作用是用来储存请求与权限的对应关系。

FilterInvocationSecurityMetadataSource 接口有 3 个方法:

  • boolean supports(Class<?> clazz):指示该类是否能够为指定的方法调用或 Web 请求提供 ConfigAttributes。
  • Collection<ConfigAttribute> getAllConfigAttributes():Spring 容器启动时自动调用, 一般把所有请求与权限的对应关系也要在这个方法里初始化, 保存在一个属性变量里。
  • Collection<ConfigAttribute> getAttributes(Object object):当接收到一个 http 请求时, filterSecurityInterceptor 会调用的方法. 参数 object 是一个包含 url 信息的 HttpServletRequest 实例. 这个方法要返回请求该 url 所需要的所有权限集合。

MyAccessDecisionManager

/**
 * 决策器
 */
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">static</span> <span class="hljs-type">Logger</span> <span class="hljs-variable">logger</span> <span class="hljs-operator">=</span> LoggerFactory.getLogger(MyAccessDecisionManager.class);

<span class="hljs-comment">/**
 * 通过传递的参数来决定用户是否有访问对应受保护对象的权限
 *
 * <span class="hljs-doctag">@param</span> authentication 包含了当前的用户信息,包括拥有的权限。这里的权限来源就是前面登录时UserDetailsService中设置的authorities。
 * <span class="hljs-doctag">@param</span> object  就是FilterInvocation对象,可以得到request等web资源
 * <span class="hljs-doctag">@param</span> configAttributes configAttributes是本次访问需要的权限
 */</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">decide</span><span class="hljs-params">(Authentication authentication, Object object, Collection&lt;ConfigAttribute&gt; configAttributes)</span> <span class="hljs-keyword">throws</span> AccessDeniedException, InsufficientAuthenticationException {
    <span class="hljs-keyword">if</span> (<span class="hljs-literal">null</span> == configAttributes || <span class="hljs-number">0</span> &gt;= configAttributes.size()) {
        <span class="hljs-keyword">return</span>;
    } <span class="hljs-keyword">else</span> {
        String needRole;
        <span class="hljs-keyword">for</span>(Iterator&lt;ConfigAttribute&gt; iter = configAttributes.iterator(); iter.hasNext(); ) {
            needRole = iter.next().getAttribute();

            <span class="hljs-keyword">for</span>(GrantedAuthority ga : authentication.getAuthorities()) {
                <span class="hljs-keyword">if</span>(needRole.trim().equals(ga.getAuthority().trim())) {
                    <span class="hljs-keyword">return</span>;
                }
            }
        }
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">AccessDeniedException</span>(<span class="hljs-string">"当前访问没有权限"</span>);
    }

}

<span class="hljs-comment">/**
 * 表示此AccessDecisionManager是否能够处理传递的ConfigAttribute呈现的授权请求
 */</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">supports</span><span class="hljs-params">(ConfigAttribute configAttribute)</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}

<span class="hljs-comment">/**
 * 表示当前AccessDecisionManager实现是否能够为指定的安全对象(方法调用或Web请求)提供访问控制决策
 */</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">supports</span><span class="hljs-params">(Class&lt;?&gt; aClass)</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}

}

MyAccessDecisionManager 类实现了 AccessDecisionManager 接口,AccessDecisionManager 是由 AbstractSecurityInterceptor 调用的,它负责鉴定用户是否有访问对应资源(方法或 URL)的权限。

MyFilterSecurityInterceptor

@Component
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> FilterInvocationSecurityMetadataSource securityMetadataSource;

<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setMyAccessDecisionManager</span><span class="hljs-params">(MyAccessDecisionManager myAccessDecisionManager)</span> {
    <span class="hljs-built_in">super</span>.setAccessDecisionManager(myAccessDecisionManager);
}


<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">doFilter</span><span class="hljs-params">(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)</span> <span class="hljs-keyword">throws</span> IOException, ServletException {

    <span class="hljs-type">FilterInvocation</span> <span class="hljs-variable">fi</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">FilterInvocation</span>(servletRequest, servletResponse, filterChain);
    invoke(fi);
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">invoke</span><span class="hljs-params">(FilterInvocation fi)</span> <span class="hljs-keyword">throws</span> IOException, ServletException {

    <span class="hljs-type">InterceptorStatusToken</span> <span class="hljs-variable">token</span> <span class="hljs-operator">=</span> <span class="hljs-built_in">super</span>.beforeInvocation(fi);
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">//执行下一个拦截器</span>
        fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
    } <span class="hljs-keyword">finally</span> {
        <span class="hljs-built_in">super</span>.afterInvocation(token, <span class="hljs-literal">null</span>);
    }
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> Class&lt;?&gt; getSecureObjectClass() {
    <span class="hljs-keyword">return</span> FilterInvocation.class;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> SecurityMetadataSource <span class="hljs-title function_">obtainSecurityMetadataSource</span><span class="hljs-params">()</span> {

    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.securityMetadataSource;
}

}

每种受支持的安全对象类型(方法调用或 Web 请求)都有自己的拦截器类,它是 AbstractSecurityInterceptor 的子类,AbstractSecurityInterceptor 是一个实现了对受保护对象的访问进行拦截的抽象类。

AbstractSecurityInterceptor 的机制可以分为几个步骤:

    1. 查找与当前请求关联的“配置属性(简单的理解就是权限)”
    1. 将 安全对象(方法调用或 Web 请求)、当前身份验证、配置属性 提交给决策器(AccessDecisionManager)
    1. (可选)更改调用所根据的身份验证
    1. 允许继续进行安全对象调用 (假设授予了访问权)
    1. 在调用返回之后,如果配置了 AfterInvocationManager。如果调用引发异常,则不会调用 AfterInvocationManager。

AbstractSecurityInterceptor 中的方法说明:

  • beforeInvocation() 方法实现了对访问受保护对象的权限校验,内部用到了 AccessDecisionManager 和 AuthenticationManager;
  • finallyInvocation()方法用于实现受保护对象请求完毕后的一些清理工作,主要是如果在 beforeInvocation() 中改变了 SecurityContext,则在 finallyInvocation() 中需要将其恢复为原来的 SecurityContext,该方法的调用应当包含在子类请求受保护资源时的 finally 语句块中。
  • afterInvocation()方法实现了对返回结果的处理,在注入了 AfterInvocationManager 的情况下默认会调用其 decide() 方法。

了解了 AbstractSecurityInterceptor,就应该明白了,我们自定义 MyFilterSecurityInterceptor 就是想使用我们之前自定义的 AccessDecisionManager 和 securityMetadataSource。

SecurityConfig

@EnableWebSecurity 注解以及 WebSecurityConfigurerAdapter 一起配合提供基于 web 的 security。自定义类 继承了 WebSecurityConfigurerAdapter 来重写了一些方法来指定一些特定的 Web 安全设置。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> MyUserDetailsService userService;


<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">configureGlobal</span><span class="hljs-params">(AuthenticationManagerBuilder auth)</span> <span class="hljs-keyword">throws</span> Exception {

    <span class="hljs-comment">//校验用户</span>
    auth.userDetailsService( userService ).passwordEncoder( <span class="hljs-keyword">new</span> <span class="hljs-title class_">PasswordEncoder</span>() {
        <span class="hljs-comment">//对密码进行加密</span>
        <span class="hljs-meta">@Override</span>
        <span class="hljs-keyword">public</span> String <span class="hljs-title function_">encode</span><span class="hljs-params">(CharSequence charSequence)</span> {
            System.out.println(charSequence.toString());
            <span class="hljs-keyword">return</span> DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
        }
        <span class="hljs-comment">//对密码进行判断匹配</span>
        <span class="hljs-meta">@Override</span>
        <span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">matches</span><span class="hljs-params">(CharSequence charSequence, String s)</span> {
            <span class="hljs-type">String</span> <span class="hljs-variable">encode</span> <span class="hljs-operator">=</span> DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
            <span class="hljs-type">boolean</span> <span class="hljs-variable">res</span> <span class="hljs-operator">=</span> s.equals( encode );
            <span class="hljs-keyword">return</span> res;
        }
    } );

}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">configure</span><span class="hljs-params">(HttpSecurity http)</span> <span class="hljs-keyword">throws</span> Exception {
    http.authorizeRequests()
            .antMatchers(<span class="hljs-string">"/"</span>,<span class="hljs-string">"index"</span>,<span class="hljs-string">"/login"</span>,<span class="hljs-string">"/login-error"</span>,<span class="hljs-string">"/401"</span>,<span class="hljs-string">"/css/**"</span>,<span class="hljs-string">"/js/**"</span>).permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin().loginPage( <span class="hljs-string">"/login"</span> ).failureUrl( <span class="hljs-string">"/login-error"</span> )
            .and()
            .exceptionHandling().accessDeniedPage( <span class="hljs-string">"/401"</span> );
    http.logout().logoutSuccessUrl( <span class="hljs-string">"/"</span> );
}

}

MainController

@Controller
public class MainController {
<span class="hljs-meta">@RequestMapping("/")</span>
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">root</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"redirect:/index"</span>;
}

<span class="hljs-meta">@RequestMapping("/index")</span>
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">index</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"index"</span>;
}

<span class="hljs-meta">@RequestMapping("/login")</span>
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">login</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"login"</span>;
}

<span class="hljs-meta">@RequestMapping("/login-error")</span>
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">loginError</span><span class="hljs-params">(Model model)</span> {
    model.addAttribute( <span class="hljs-string">"loginError"</span>  , <span class="hljs-literal">true</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-string">"login"</span>;
}

<span class="hljs-meta">@GetMapping("/401")</span>
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">accessDenied</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"401"</span>;
}

<span class="hljs-meta">@GetMapping("/user/common")</span>
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">common</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"user/common"</span>;
}

<span class="hljs-meta">@GetMapping("/user/admin")</span>
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">admin</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"user/admin"</span>;
}

}

页面

login.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
    <h1>Login page</h1>
    <p th:if="${loginError}" class="error">用户名或密码错误</p>
    <form th:action="@{/login}" method="post">
        <label for="username">用户名</label>:
        <input type="text" id="username" name="username" autofocus="autofocus" />
        <br/>
        <label for="password">密 码</label>:
        <input type="password" id="password" name="password" />
        <br/>
        <input type="submit" value="登录" />
    </form>
    <p><a href="/index" th:href="@{/index}"></a></p>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <h2>page list</h2>
    <a href="/user/common">common page</a>
    <br/>
    <a href="/user/admin">admin page</a>
    <br/>
    <form th:action="@{/logout}" method="post">
        <input type="submit" class="btn btn-primary" value="注销"/>
    </form>
</body>
</html>

admin.html

<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <title>admin page</title>
</head>
<body>
    success admin page!!!
</body>
</html>

common.html

<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <title>common page</title>
</head>
<body>
    success common page!!!
</body>
</html>

401.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>401 page</title>
</head>
<body>
    <div>
        <div>
            <h2>权限不够</h2>
            <p>拒绝访问!</p>
        </div>
    </div>
</body>
</html>

最后运行项目,可以分别用 user、admin 账号 去测试认证和授权是否正确。

参考

《深入理解 Spring Cloud 与微服务构建》

https://www.ktanx.com/blog/p/4929

源码

https://github.com/gf-huanchupk/SpringBootLearning/tree/master/springboot-security


欢迎扫码或微信搜索公众号《程序员果果》关注我,关注有惊喜 ~