权限框架 | Spring Security入门教程

文章目录:

▶ 简介

▶ 框架搭建

    ▶ 环境要求

    ▶ 工程搭建

▶ 框架分析

    ▶ 跳转登录页面

    ▶ 用户名和密码

▶ 框架核心过滤器

▶ 框架核心组件

    ▶ Authentication

    ▶ SecurityContextHolder

    ▶ AuthenticationManager

    ▶ AuthenticationProvider

    ▶ UserDetailService

    ▶ GrantedAuthority

▶ 认证流程梳理

▶ SpringBoot整合Spring Security

▶ 资料下载地址

 

简介

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架(简单说是对访问权限进行控制 )。

它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IOC,DI和AOP功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

应用的安全性包括:用户认证(Authentication)用户授权(Authorization)两个部分

用户认证:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统 。

  • 用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程

用户授权:验证某个用户是否有权限执行某个操作

  • 在一个系统中,不同用户所具有的权限是不同的

  • 比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改

  • 一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限

框架搭建

环境要求:

    SpringBoot、SpringSecurity、MySQL

工程搭建:

  • 创建一个SpringBoot项目,并引入相关包

 5    <parent>
 6        <groupId>org.springframework.boot</groupId>
 7        <artifactId>spring-boot-starter-parent</artifactId>
 8        <version>2.1.5.RELEASE</version>
 9        <relativePath/> <!-- lookup parent from repository -->
10    </parent>
21    <dependencies>
22        <dependency>
23            <groupId>org.springframework.boot</groupId>
24            <artifactId>spring-boot-starter-security</artifactId>
25        </dependency>
26        <dependency>
27            <groupId>org.springframework.boot</groupId>
28            <artifactId>spring-boot-starter-thymeleaf</artifactId>
29        </dependency>
30        <dependency>
31            <groupId>org.springframework.boot</groupId>
32            <artifactId>spring-boot-starter-web</artifactId>
33        </dependency>
34        <dependency>
35            <groupId>org.projectlombok</groupId>
36            <artifactId>lombok</artifactId>
37            <optional>true</optional>
38        </dependency>
39        <dependency>
40            <groupId>org.springframework.boot</groupId>
41            <artifactId>spring-boot-starter-test</artifactId>
42            <scope>test</scope>
43        </dependency>
44        <dependency>
45            <groupId>org.springframework.security</groupId>
46            <artifactId>spring-security-test</artifactId>
47            <scope>test</scope>
48        </dependency>
49    </dependencies>

      构建启动类

1@SpringBootApplication
2public class SpringsecuritydemoApplication {
3
4    public static void main(String[] args) {
5        SpringApplication.run(SpringsecuritydemoApplication.class, args);
6    }
7
8}

       启动项目,访问即可:http://localhost:8080

  • 输入用户名和密码

    • 用户名:user

    • 密码:可以通过启动控制台查看

    • Using generated security password

框架分析

浏览器,访问http://localhost:8080,发现会跳转到第一个登录页面,Spring Security已经默认做了一些配置,并且创建一个简单的登录页面 ,那这个页面是怎么来的?通过跟踪源码来一探究竟。

Spring Security 官网文档链接:

https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#jc-oauth2login

跳转登录页面

通过查看文档发现,WebSecurityConfigurerAdapter 提供的默认的配置 ,这个抽象类中,提供了一个方法formLogin(),内容如下:

 1protected void configure(HttpSecurity http) throws Exception {
 2        logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
 3
 4        http
 5            .authorizeRequests()
 6                .anyRequest().authenticated()
 7                .and()
 8            .formLogin().and()
 9            .httpBasic();
10    }

 

1public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
2        return getOrApply(new FormLoginConfigurer<>());
3    }

查看formLogin()源码,跳转到HttpSecurity类中,这个方法返回一个 FormLoginConfigurer<HttpSecurity>类型,再继续来看这个FormLoginConfigurer,在FormLoginConfigurer中有个initDefaultLoginFilter()方法

 1private void initDefaultLoginFilter(H http) {
 2        DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = http
 3                .getSharedObject(DefaultLoginPageGeneratingFilter.class);
 4        if (loginPageGeneratingFilter != null && !isCustomLoginPage()) {
 5            loginPageGeneratingFilter.setFormLoginEnabled(true);
 6            loginPageGeneratingFilter.setUsernameParameter(getUsernameParameter());
 7            loginPageGeneratingFilter.setPasswordParameter(getPasswordParameter());
 8            loginPageGeneratingFilter.setLoginPageUrl(getLoginPage());
 9            loginPageGeneratingFilter.setFailureUrl(getFailureUrl());
10            loginPageGeneratingFilter.setAuthenticationUrl(getLoginProcessingUrl());
11        }
12    }

这个方法,初始化一个默认登录页的过滤器,可以看到第一句代码,默认的过滤器是DefaultLoginPageGeneratingFilter ,进入过滤器中 :

 1public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
 2            throws IOException, ServletException {
 3        HttpServletRequest request = (HttpServletRequest) req;
 4        HttpServletResponse response = (HttpServletResponse) res;
 5
 6        boolean loginError = isErrorPage(request);
 7        boolean logoutSuccess = isLogoutSuccess(request);
 8        if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
 9            String loginPageHtml = generateLoginPageHtml(request, loginError,
10                    logoutSuccess);
11            .........
14
15            return;
16        }
17
18        chain.doFilter(request, response);
19    }

可以看到,如果没有配置login页,这个过滤器会被创建,然后看doFilter()方法,登录页面的配置是通过generateLoginPageHtml()方法创建的,再来看这个方法 :

 1private String generateLoginPageHtml(HttpServletRequest request, boolean loginError,
 2            boolean logoutSuccess) {
 3        String errorMsg = "Invalid credentials";
 4
 5        if (loginError) {
 6            HttpSession session = request.getSession(false);
 7
 8            if (session != null) {
 9                AuthenticationException ex = (AuthenticationException) session
10                        .getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
11                errorMsg = ex != null ? ex.getMessage() : "Invalid credentials";
12            }
13        }
14
15        StringBuilder sb = new StringBuilder();
16
17        sb.append("<!DOCTYPE html>\n"
18                + "<html lang=\"en\">\n"
19                + "  <head>.......");
30
31        String contextPath = request.getContextPath();
32        if (this.formLoginEnabled) {
33            sb.append("      <form class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.authenticationUrl + "\">\n"
34                    + "........");
49        }
50
51        if (openIdEnabled) {
52            sb.append("      <form name=\"oidf\" class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.openIDauthenticationUrl + "\">\n"
53                  .......
64        }
65
66        if (oauth2LoginEnabled) {
67            sb.append("<h2 class=\"form-signin-heading\">Login with OAuth 2.0</h2>");
68           .......
71            for (Map.Entry<String, String> clientAuthenticationUrlToClientName : oauth2AuthenticationUrlToClientName.entrySet()) {
72                    .........
79            }
80            sb.append("</table>\n");
81        }
82        sb.append("</div>\n");
83        sb.append("</body></html>");
84
85        return sb.toString();
86    }

至此,默认登录页及配置,已经可以清楚了 。

用户名和密码分析

在项目启动的日志中,可以发现有这样一条信息 :

12019-05-20 16:26:24.846  INFO 9032 --- [           main] .s.s.UserDetailsServiceAutoConfiguration : 
2
3Using generated security password: 5baa53a8-2ff7-4fea-9e10-185b5f640dad

可以看到,自动配置类是UserDetailsServiceAutoConfiguration,密码是 :5baa53a8-2ff7-4fea-9e10-185b5f640dad,现在知道了密码,那用户名是什么还不知道,进入到 UserDetailsServiceAutoConfiguration去看看:

在这个 UserDetailsServiceAutoConfiguration 类的描述中可以知道,这个类是设置一些 Spring Security 相关默认的自动配置,把InMemoryUserDetailsManager 中得user 和 password 信息设置为默认得用户和密码,可以通过提供的AuthenticationManager、AuthenticationProvider 或者 UserDetailsService 的 bean 来覆盖默认的自动配置信息:

 1@Bean
 2    @ConditionalOnMissingBean(
 3            type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")
 4    @Lazy
 5    public InMemoryUserDetailsManager inMemoryUserDetailsManager(
 6            SecurityProperties properties,
 7            ObjectProvider<PasswordEncoder> passwordEncoder) {
 8        SecurityProperties.User user = properties.getUser();
 9        List<String> roles = user.getRoles();
10        return new InMemoryUserDetailsManager(User.withUsername(user.getName())
11                .password(getOrDeducePassword(user,  passwordEncoder.getIfAvailable()))
12                .roles(StringUtils.toStringArray(roles)).build());
13    }
14
15private String getOrDeducePassword(SecurityProperties.User user,
16            PasswordEncoder encoder) {
17        String password = user.getPassword();
18        if (user.isPasswordGenerated()) {
19            logger.info(String.format("%n%nUsing generated security password: %s%n",
20                    user.getPassword()));
21        }
22        if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
23            return password;
24        }
25        return NOOP_PASSWORD_PREFIX + password;
26    }

可以看到, 日志输出的密码是通过inMemoryUserDetailsManager()方法获取,返回一个新的带有UserDetials信息参数构造的InMemoryUSerDetailsManager对象 ,第一个参数为:User.withUsername(user.getName()),其中user 对象是上面SecurityProperties.User类型 的,通过SecurityProperties 对象中获取的 ,看下SecurityProperties类 :

 1@ConfigurationProperties(prefix = "spring.security")
 2public class SecurityProperties {
 3    private User user = new User();
 4
 5    public User getUser() {
 6        return this.user;
 7    }
 8
 9    public static class User {  
10        private String name = "user";

11        private String password = UUID.randomUUID().toString();

12    }

13}

通过配置文件中的,前缀为spring.security 的配置可以改变默认配置信息,再看看SecurityProperties 的 getUser()方法 ,通过一步步的跟踪,发现默认的用户名是user

框架核心过滤器

想要对WEB资源进行保护,最好的办法就是Filter,想要对方法进行保护,最好的办法就是AOP,SpringSecurity在我们进行用户认证和授权的时候,会通过各种各样的拦截器来控制权限的访问,从而实现安全。SpringSecurity常见的过滤器有:

Filter含义
WebAsyncManagerIntegrationFilter异步 , 提供了对securityContext和WebAsyncManager的集成
SecurityContextPersistenceFilter同步 , 从配置的SecurityContextRepository而不是request中获取信息存到SecurityContextHolder,并且当请求结束清理contextHolder时将值存回repository中(默认使用HttpSessionSecurityContextRepository).在该过滤器中每一个请求仅执行一次,该filter需在任何认证处理机制其作用之前执行。认证处理机制如basic,cas等期望在执行时从SecurityContextHolder中获取SecurityContext
HeaderWriterFilter是一个向HttpServletResponse写入http请求头的约定
CsrfFilter通过使用同步token模式来进行csrf防护
LogoutFilter记录用户的退出
RequestCacheAwareFilter用于用户登录成功后,重新恢复因为登录被打断的请求 , 请求信息被保存到cache中
SecurityContextHolderAwareRequestFilter包装请求对象request
AnonymousAuthenticationFilter是在UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter、RememberMeAuthenticationFilter这些过滤器后面的,所以如果这三个过滤器都没有认证成功,则为当前的SecurityContext中添加一个经过匿名认证的token,但是通过servlet的getRemoteUser等方法是获取不到登录账号的。因为SecurityContextHolderAwareRequestFilter过滤器在AnonymousAuthenticationFilter前面
SessionManagementFilter管理session
ExceptionTranslationFilter处理过滤器链抛出的所有AccessDeniedException和AuthenticationException异常
FilterSecurityInterceptor通过实现了filter来增加http资源的安全性。这个安全拦截器需要FilterInvocationSecurityMedataSource
UsernamePasswordAuthenticationFilter登陆用户密码验证过滤器 ,基于用户名和密码的认证逻辑
BasicAuthenticationFilter处理一个http请求的basic认证头,将结果放入SecurityContextHolder
DefaultLoginPageGeneratingFilter当一个用户没有配置login页面时使用。仅当跳转到login页面时用到

核心组件

Authentication

  • Authentication 是一个接口,用来表示用户认证信息的

  • 在用户登录认证之前相关信息会封装为一个Authentication 具体实现类的对象 ,在登录认证成功之后又会生成一个信息更全面,包含用户权限等信息的 Authentication 对象,然后把它保存在 SecurityContextHolder 所持有的 SecurityContext 中,供后续的程序进行调用

  • Authentication 对象不需要我们自己去创建,在与系统交互的过程中,Spring Security 会自动为我们创建相应的 Authentication 对象 ,然后赋值给当前的 SecurityContext ,但是往往我们需要在程序中获取当前用户的相关信息,比如最常见的是获取当前登录用户的用户名。在程序的任何地方,通过如下方式我们可以获取到当前用户的用户名

 1public String getCurrentUsername() {
 2      Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
 3      if (principal instanceof UserDetails) {
 4         return ((UserDetails) principal).getUsername();
 5      }
 6      if (principal instanceof Principal) {
 7         return ((Principal) principal).getName();
 8      }
 9      return String.valueOf(principal);
10  }

        通过 Authentication.getPrincipal() 可以获取到代表当前用户的信息。

  • 获取当前用户的用户名是一种比较常见的需求,关于上述代码其实 Spring Security 在 Authentication 中的实现类中已经为我们做了相关实现,所以获取当前用户的用户名最简单的方式应当如下 :

1public String getCurrentUsername() {
2      return SecurityContextHolder.getContext().getAuthentication().getName();
3   }

        此外,调用 SecurityContextHolder.getContext() 获取 SecurityContext 时,如果对应的 SecurityContext 不存在,则 Spring Security 将为我们建立一个空的 SecurityContext 并进行返回

SecurityContextHolder

  • SecurityContextHolder 是用来保存 SecurityContext的

  • SecurityContext 中含有当前正在访问系统的用户的详细信息

  • 默认情况下,SecurityContextHolder 将使用 ThreadLocal 来保存 SecurityContext ,这也就意味着在处于同一线程中的方法中我们可以从 ThreadLocal 中获取到当前的 SecurityContext,因为线程池的原因,如果我们每次在请求完成后都将 ThreadLocal 进行清除的话,那么我们把 SecurityContext 存放在 ThreadLocal 中还是比较安全的

  • 这些工作 Spring Security 已经自动为我们做了,即在每一次 request 结束后都将清除当前线程的 ThreadLocal

  • SecurityContextHolder 中定义了一系列的静态方法,而这些静态方法内部逻辑基本上都是通过SecurityContextHolder 持有的 SecurityContextHolderStrategy 来实现的,如 getContext()、setContext() 、clearContext()等

  • Spring Security 还提供了两种类型的 strategy 实现,GlobalSecurityContextHolderStrategy 和 InheritableThreadLocalSecurityContextHolderStrategy ,前者表示全局使用同一个 SecurityContext;后者使用 InheritableThreadLocal 来存放 SecurityContext,即子线程可以使用父线程中存放的变量

  • 一般而言,我们使用默认的 strategy 就可以了,但是如果要改变默认的 strategy,Spring Security 为我们提供了两种方法,这两种方式都是通过改变 strategyName 来实现的

  • SecurityContextHolder 中为三种不同类型的 strategy 分别命名为 MODE_THREADLOCAL、MODE_INHERITABLETHREADLOCAL 和 MODE_GLOBAL ,第一种方式是通过 SecurityContextHolder 的静态方法 setStrategyName() 来指定需要使用的 strategy;第二种方式是通过系统属性进行指定,其中属性名默认为 “spring.security.strategy”,属性值为对应 strategy 的名称

AuthenticationManager

AuthenticationProvider 

  • AuthenticationManager 是一个用来处理认证(Authentication)请求的接口

  • 认证是由 AuthenticationManager 来管理的,但是真正进行认证的是 AuthenticationManager 中定义的 AuthenticationProvider。AuthenticationManager 中可以定义有多个 AuthenticationProvider

  • 在其中只定义了一个方法 authenticate(),该方法只接收一个代表认证请求的 Authentication 对象作为参数,如果认证成功,则会返回一个封装了当前用户权限等信息的 Authentication 对象进行返回

1Authentication authenticate(Authentication authentication) throws AuthenticationException;

        在 Spring Security 中,AuthenticationManager 的默认实现是 ProviderManager而且它不直接自己处理认证请求,而是委托给        其所配置的 AuthenticationProvider 列表,然后会依次使用每一个 AuthenticationProvider 进行认证

  • 如果有一个 AuthenticationProvider 认证后的结果不为 null,则表示该 AuthenticationProvider 已经认证成功,之后的 AuthenticationProvider 将不再继续认证。然后直接以该 AuthenticationProvider 的认证结果作为 ProviderManager 的认证结果

  • 如果所有的 AuthenticationProvider 的认证结果都为 null,则表示认证失败,将抛出一个 ProviderNotFoundException

  • 校验认证请求最常用的方法是根据请求的用户名加载对应的 UserDetails,然后比对 UserDetails 的密码与认证请求的密码是否一致,一致则表示认证通过

  • Spring Security 内部的 DaoAuthenticationProvider 就是使用的这种方式。其内部使用 UserDetailsService 来负责加载 UserDetails ,在认证成功以后会使用加载的 UserDetails 来封装要返回的 Authentication 对象,加载的 UserDetails 对象是包含用户权限等信息的。认证成功返回的 Authentication 对象将会保存在当前的 SecurityContext 中

  • 默认情况下,在认证成功后 ProviderManager 将清除返回的 Authentication 中的凭证信息,如密码

  • 如果你在无状态的应用中将返回的 Authentication 信息缓存起来了,那么以后你再利用缓存的信息去认证将会失败,因为它已经不存在密码这样的凭证信息了

  • 所以在使用缓存的时候你应该考虑到这个问题。一种解决办法是设置 ProviderManager 的 eraseCredentialsAfterAuthentication 属性为 false,或者想办法在缓存时将凭证信息一起缓存

UserDetailsService

  • 通过 Authentication.getPrincipal() 的返回类型是 Object,但很多情况下其返回的其实是一个 UserDetails 的实例

  • UserDetails 是 Spring Security 中一个核心的接口 ,其中定义了一些可以获取用户名、密码、权限等与认证相关的信息的方法

  • Spring Security 内部使用的 UserDetails 实现类大都是内置的 User 类,我们如果要使用 UserDetails 时也可以直接使用该类

  • 在 Spring Security 内部很多地方需要使用用户信息的时候基本上都是使用的 UserDetails,比如在登录认证的时候

  • 登录认证的时候 Spring Security 会通过 UserDetailsService 的 loadUserByUsername() 方法获取对应的 UserDetails 进行认证,认证通过后会将该 UserDetails 赋给认证通过的 Authentication 的 principal,然后再把该 Authentication 存入到 SecurityContext 中

  • 之后如果需要使用用户信息的时候就是通过 SecurityContextHolder 获取存放在 SecurityContext 中的 Authentication 的 principal

  • 通常我们需要在应用中获取当前用户的其它信息,如 Email、电话等。这时存放在 Authentication 的 principal 中只包含有认证相关信息的 UserDetails 对象可能就不能满足我们的要求了。这时我们可以实现自己的 UserDetails,在该实现类中我们可以定义一些获取用户其它信息的方法,这样将来我们就可以直接从当前 SecurityContext 的 Authentication 的 principal 中获取这些信息了

  • UserDetailsService 也是一个接口,我们也需要实现自己的 UserDetailsService 来加载我们自定义的 UserDetails 信息。然后把它指定给 AuthenticationProvider 即可

  • 另外 Spring Security 还为我们提供了 UserDetailsService 另外一个实现,InMemoryDaoImpl

  • InMemoryDaoImpl 主要是测试用的,其只是简单的将用户信息保存在内存中

GrantedAuthority

  • Authentication 的 getAuthorities() 可以返回当前 Authentication 对象拥有的权限,即当前用户拥有的权限,其返回值是一个 GrantedAuthority 类型的数组,每一个 GrantedAuthority 对象代表赋予给当前用户的一种权限

  • GrantedAuthority 是一个接口,其通常是通过 UserDetailsService 进行加载,然后赋予给 UserDetails

  • GrantedAuthority 中只定义了一个 getAuthority() 方法,该方法返回一个字符串,表示对应权限的字符串表示,如果对应权限不能用字符串表示,则应当返回 null

  • Spring Security 针对 GrantedAuthority 有一个简单实现 SimpleGrantedAuthority。该类只是简单的接收一个表示权限的字符串。Spring Security 内部的所有 AuthenticationProvider 都是使用 SimpleGrantedAuthority 来封装 Authentication 对象

认证过程梳理

  • 用户使用用户名和密码进行登录

  • Spring Security 将获取到的用户名和密码封装成一个实现了 Authentication 接口的 UsernamePasswordAuthenticationToken

  • 将上述产生的 token 对象传递给 AuthenticationManager 进行登录认证

  • AuthenticationManager 认证成功后将会返回一个封装了用户权限等信息的 Authentication 对象

  • 通过调用 SecurityContextHolder.getContext().setAuthentication(...) 将 AuthenticationManager 返回的 Authentication 对象赋予给当前的 SecurityContext

  • 在认证成功后,用户就可以继续操作去访问其它受保护的资源了,但是在访问的时候将会使用保存在 SecurityContext 中的 Authentication 对象进行相关的权限鉴定,如不存在对应的访问权限,则会返回 403 错误码

SpringBoot整合SpringSecurity

  • 新建Maven工程,并导入以下包:

 1<dependencies>
 2        <dependency>
 3            <groupId>org.springframework.boot</groupId>
 4            <artifactId>spring-boot-starter-security</artifactId>
 5        </dependency>
 6        <dependency>
 7            <groupId>org.springframework.boot</groupId>
 8            <artifactId>spring-boot-starter-thymeleaf</artifactId>
 9        </dependency>
10        <dependency>
11            <groupId>org.springframework.boot</groupId>
12            <artifactId>spring-boot-starter-web</artifactId>
13        </dependency>
14        <dependency>
15            <groupId>org.mybatis.spring.boot</groupId>
16            <artifactId>mybatis-spring-boot-starter</artifactId>
17            <version>2.0.1</version>
18        </dependency>
19
20        <dependency>
21            <groupId>mysql</groupId>
22            <artifactId>mysql-connector-java</artifactId>
23            <version>5.1.24</version>
24        </dependency>
25        <dependency>
26            <groupId>org.projectlombok</groupId>
27            <artifactId>lombok</artifactId>
28            <optional>true</optional>
29        </dependency>
30        <dependency>
31            <groupId>org.springframework.boot</groupId>
32            <artifactId>spring-boot-starter-test</artifactId>
33            <scope>test</scope>
34        </dependency>
35        <dependency>
36            <groupId>org.springframework.security</groupId>
37            <artifactId>spring-security-test</artifactId>
38            <scope>test</scope>
39        </dependency>
40    </dependencies>

          准备数据库表结构

1CREATE TABLE `user` (
2  `userId` bigint(20) NOT NULL AUTO_INCREMENT,
3  `username` varchar(100) DEFAULT NULL,
4  `password` varchar(200) DEFAULT NULL,
5  `phone` varchar(20) DEFAULT NULL,
6  PRIMARY KEY (`userId`)
7) ENGINE=InnoDB DEFAULT CHARSET=utf8

          新建实体对象类

 1@Data
 2public class User implements UserDetails {
 3    private Long userId;
 4    private String username;
 5    private String password;
 6    private String phone;
 7
 8    private List<GrantedAuthority> authorities;
 9
10    @Override
11    public Collection<? extends GrantedAuthority> getAuthorities() {
12        return this.authorities;
13    }
14
15    @Override
16    public boolean isAccountNonExpired() {
17        return true;
18    }
19
20    @Override
21    public boolean isAccountNonLocked() {
22        return true;
23    }
24
25    @Override
26    public boolean isCredentialsNonExpired() {
27        return true;
28    }
29
30    @Override
31    public boolean isEnabled() {
32        return true;
33    }
34}

      配置application.xml文件

 1# 访问端口号
 2server.port=8890
 3
 4# 数据库相关配置
 5spring.datasource.driver-class-name=com.mysql.jdbc.Driver
 6spring.datasource.url=jdbc:mysql://localhost:3306/securitydemo
 7spring.datasource.username=root
 8spring.datasource.password=123456
 9
10# MyBatis的相关配置
11# 映射文件位置
12mybatis.mapper-locations=classpath:mapper/*.xml
13# 输出SQL执行语句
14mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

           增加前端登录和首页面

 1login.html:
10        <form th:action="@{/login}" method="post">
11            <input type="text" id="username" name="username" placeholder="手机号"/>
12            <br/>
13            <input type="password" id="password" name="password" placeholder="密码"/>
14            <br/>
15            <p th:if="${param.authError}" style="color: red">用户名或者密码错误</p>
16            <br/>
17            <button type="submit">登录</button>
18        </form>

前台页面

 1index.html:
 2
 3<!DOCTYPE html>
 4<html lang="en" xmlns:th="http://www.thymeleaf.org">
 5<head>
 6    <meta charset="UTF-8">
 7    <title>首页面</title>
 8</head>
 9<body>
10
11<h3>登录成功</h3>
12<br/>
13
14<form th:action="@{/user/logout}" method="post" id="logoutForm">
15    <button type="submit" form="logoutForm">注销</button>
16</form>
17
18</body>
19</html>

        增加接口页面跳转控制器

 1@Controller
 2public class LoginController {
 3
 4    @Autowired
 5    private IUserService userService;
 6
 7    @GetMapping("/user/toLogin")
 8    public String toLogin(){
 9        return "login";
10    }
11
12    @PostMapping("/user/logout")
13    public String logout(){
14        return "login";
15    }
16
17}

       增加权限控制等类

 1@Configuration
 2@EnableWebSecurity
 3public class MySecurityConfig extends WebSecurityConfigurerAdapter {
 4
 5    @Override
 6    protected void configure(HttpSecurity http) throws Exception {
 7       http.authorizeRequests()
 8               .antMatchers("/user/toLogin").permitAll()
 9               .and()
10               .formLogin()
11               .loginProcessingUrl("/login")
12               .failureHandler(authFailHandler())
13               .and();
14    }
15
16    @Autowired
17    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
24        auth.authenticationProvider(authProvider());
25
26    }
27
28    @Bean
29    public MyAuthProvider authProvider(){
30        return new MyAuthProvider();
31    }
32
33    @Bean
34    public MyAuthFailHandler authFailHandler(){
35        return new MyAuthFailHandler();
36    }
37}

 

 1public class MyAuthProvider implements AuthenticationProvider {
 2
 3    @Autowired
 4    private IUserService userService;
 5
 6    BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
 7
 8    @Override
 9    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
10
11        String name = authentication.getName();//获取表单提交的用户名
12        String passwordForm = (String) authentication.getCredentials();//获取表单输入的密码
13
14        User user = userService.queryUserByName(name);
15        if (user==null) {
16            throw new AuthenticationCredentialsNotFoundException("authError");
17        }
18        if(bCryptPasswordEncoder.matches(passwordForm, user.getPassword())){
19            return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
20        }else{
21            throw new BadCredentialsException("authError");
22        }
25    }
26
27    @Override
28    public boolean supports(Class<?> authentication) {
29        return true;
30    }
31}

 

 1public class MyAuthFailHandler extends SimpleUrlAuthenticationFailureHandler {
 2
 3    @Override
 4    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
 5
 6
 7        super.setDefaultFailureUrl("/user/toLogin?"+exception.getMessage());
 8        super.onAuthenticationFailure(request, response, exception);
 9    }
10}

      增加接口和mybatis信息

1public interface IUserService {
2
3    public User queryUserByName(String username);
4
5}

 

 1@Service
 2public class UserServiceImpl implements IUserService {
 3
 4    @Autowired
 5    UserDao userDao;
 6
 7    @Override
 8    public User queryUserByName(String username) {
 9        User user = userDao.queryUserByName(username);
10        return user;
11    }
12}

 

 6<mapper namespace="com.mysecurity.dao.UserDao">
 7
 8    <select id="queryUserByName" resultType="com.mysecurity.entity.User">
 9        select * from user where username=#{username}
10    </select>
11</mapper>

 

1public interface UserDao {
2
3    public User queryUserByName(String username);
4}

   启动运行、访问即可

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值