Spring in action读书笔记(七) Spring Security启用权限管理实践

所需jar包

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<servletApiVersion>3.1.0</servletApiVersion>
<springSecurityVersion>3.2.0.RELEASE</springSecurityVersion>
<springVersion>4.0.7.RELEASE</springVersion>
<thymeleafVersion>2.1.3.RELEASE</thymeleafVersion>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${springVersion}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${springVersion}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${springSecurityVersion}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${springSecurityVersion}</version>
</dependency>


<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId>
<version>${thymeleafVersion}</version>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity3</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servletApiVersion}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
</dependencies>

一  启用Spring Security最简单的配置

1、MVC启用类,配置thymeleaf

WebInitializer.java

package test;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
  
  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Class<?>[] { RootConfig.class };
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class<?>[] { WebConfig.class };
  }

  @Override
  protected String[] getServletMappings() {
    return new String[] { "/" };
  }
}

WebConfig.java

package test;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.thymeleaf.extras.springsecurity3.dialect.SpringSecurityDialect;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
import org.thymeleaf.templateresolver.TemplateResolver;

@Configuration
@EnableWebMvc
@ComponentScan("test")
public class WebConfig extends WebMvcConfigurerAdapter {

  @Bean
  public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
    ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
    viewResolver.setTemplateEngine(templateEngine);
    return viewResolver;
  }

  @Bean
  public SpringTemplateEngine templateEngine(TemplateResolver templateResolver) {
    SpringTemplateEngine templateEngine = new SpringTemplateEngine();
    templateEngine.setTemplateResolver(templateResolver);
    templateEngine.addDialect(new SpringSecurityDialect());
    return templateEngine;
  }

  @Bean
  public TemplateResolver templateResolver() {
    TemplateResolver templateResolver = new ServletContextTemplateResolver();
    templateResolver.setPrefix("/WEB-INF/templates/");
    templateResolver.setSuffix(".html");
    templateResolver.setTemplateMode("HTML5");
    return templateResolver;
  }
}

RootConfig.java

package test;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan(basePackages={"test"},
    excludeFilters={
        @Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)
    })
public class RootConfig {

}

2、过滤web请求,将web请求委托给Spring Security相关的Filter

package test;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer {
}

3、web应用启用Spring Security

package test;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {


}

访问http://localhost:8080/会链接到http://localhost:8080/login,即Spring security提供的基本登录也功能。默认要求所有的http请求都要认证。

相当于SecurityConfig类重写configure(HttpSecurity)方法

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin().and()
                .httpBasic();
    }

 二  基于内存用户认证

基于内存配置用户,SecurityConfig中重写configure(AuthenticationManagerBuilder)方法,并且配置除首页外其他路径都需要进行认证。

package test;

import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.Objects;


@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/home", "/").permitAll()
                .anyRequest().authenticated()
                .and()
                //启用默认登录页面
                .formLogin().and()
                .httpBasic();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.inMemoryAuthentication()
                //密码加密方法,这里简单地反转字符串, 设置的密码是加密后的密码, 因此可使用 user作为用户名、password作为密码进行登录
                .passwordEncoder(new ReversePasswordEncoder())
                .withUser("user").password(StringUtils.reverse("password")).authorities("ROLE_USER")
                .and()
                .withUser("admin").password(StringUtils.reverse("password")).authorities("ROLE_USER", "ROLE_ADMIN");
    }

    private static class ReversePasswordEncoder implements PasswordEncoder {

        @Override
        public String encode(CharSequence rawPassword) {
            return StringUtils.reverse(String.valueOf(rawPassword));
        }

        @Override
        public boolean matches(CharSequence rawPassword, String encodedPassword) {
            return Objects.equals(encode(rawPassword), encodedPassword);
        }
    }

}

配置controller,创建可访问的html页面

package test;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import static org.springframework.web.bind.annotation.RequestMethod.GET;

@Controller
@RequestMapping("/")
public class HomeController {

    @RequestMapping(method = GET, value = {"", "/home"})
    public String home(Model model) {
        return "home";
    }

    @RequestMapping(method = GET, value = "/authenticate")
    public String authenticate() {
        return "authenticate";
    }
}

在webapp/WEB-INF/templates目录(与WebConfig中TemplateResolver  bean相对应)下新建home.html、authenticate.html文件

home.html

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>Home</title>
  </head>
  <body>
    Home Page
  </body>
</html>

authenticate.html

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Title</title>
</head>
<body>
Authenticated
</body>
</html>

启动Spring应用, 不登录时可访问http://localhost:8080、http://localhost:8080/home,访问http://localhost:8080/authenticate 会跳转到登录页面,

登录后访问 http://localhost:8080/authenticate (登录用户名为user、admin,密码为password。localhost:8080/logout 会退出登录)

 三  用户认证、权限管理及自定义用户服务(如从Redis等NoSql数据库中取用户数据)

修改HomeController类,增加html页面,并创建admin.html文件

package test;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import static org.springframework.web.bind.annotation.RequestMethod.GET;

@Controller
@RequestMapping("/")
public class HomeController {

    @RequestMapping(method = GET, value = {"", "/home"})
    public String home(Model model) {
        return "home";
    }

    @RequestMapping(method = GET, value = "/authenticate")
    public String authenticate() {
        return "authenticate";
    }

    @RequestMapping(method = GET, value = "/admin")
    public String admin() {
        return "admin";
    }
}

admin.html

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Admin</title>
</head>
<body>
Admin
</body>
</html>

修改SecurityConfig类,配置页面访问所需权限,首页home.html允许所有用户访问,admin.html允许ADMIN用户访问,其他页面允许认证用户访问

package test;

import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.Objects;


@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/home", "/").permitAll()
                .antMatchers("/admin").hasAuthority("ROLE_ADMIN")
                .anyRequest().authenticated()
                .and()
                //启用默认登录页面
                .formLogin().and()
                .httpBasic();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //自定义的用户服务
        auth.userDetailsService(new UserDetailServiceImpl()).passwordEncoder(new ReversePasswordEncoder());
    }

    private static class ReversePasswordEncoder implements PasswordEncoder {

        @Override
        public String encode(CharSequence rawPassword) {
            return StringUtils.reverse(String.valueOf(rawPassword));
        }

        @Override
        public boolean matches(CharSequence rawPassword, String encodedPassword) {
            return Objects.equals(encode(rawPassword), encodedPassword);
        }
    }

}

其中UserDetailServiceImpl类如下,配置了两个用户数据(用户数据可从数据库中获取)

package test;

import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.Arrays;

public class UserDetailServiceImpl implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if ("user".equals(username)) {
            return new User("user", StringUtils.reverse("password"), Arrays.asList(new Role("ROLE_USER")));
        } else if ("admin".equals(username)) {
            return new User("admin", StringUtils.reverse("password"), Arrays.asList(new Role("ROLE_USER"), new Role("ROLE_ADMIN")));
        }

        throw new UsernameNotFoundException(String.format("User '%s' no found", username));
    }

    private static class Role implements GrantedAuthority {

        private String role;

        private Role(String role) {
            this.role = role;
        }

        @Override
        public String getAuthority() {
            return String.valueOf(role);
        }
    }

}

启动应用程序,其中admin账号(密码password)可以访问 http://localhost:8080/admin、http://localhost:8080/authenticate、http://localhost:8080/home,

                        user账号(密码password)可以访问http://localhost:8080/authenticate、http://localhost:8080/home, 访问http://localhost:8080/admin 会报403错误。

                       不登录情况下只能访问http://localhost:8080/home

四  自定义登录页面

修改WebConfig类,增加/login对应的视图

package test;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.thymeleaf.extras.springsecurity3.dialect.SpringSecurityDialect;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
import org.thymeleaf.templateresolver.TemplateResolver;

@Configuration
@EnableWebMvc
@ComponentScan("test")
public class WebConfig extends WebMvcConfigurerAdapter {

    @Bean
    public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setTemplateEngine(templateEngine);
        return viewResolver;
    }

    @Bean
    public SpringTemplateEngine templateEngine(TemplateResolver templateResolver) {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);
        templateEngine.addDialect(new SpringSecurityDialect());
        return templateEngine;
    }

    @Bean
    public TemplateResolver templateResolver() {
        TemplateResolver templateResolver = new ServletContextTemplateResolver();
        templateResolver.setPrefix("/WEB-INF/templates/");
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode("HTML5");
        return templateResolver;
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
    }
}

新建thymeleaf模板HTML: home.html及page.html

home.html

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Test-login</title>
</head>
<body onload='document.f.username.focus();'>
<div id="header" th:include="page :: header"></div>
<div id="content">
    <form name='f' th: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 id="remember_me" name="remember-me" type="checkbox"/>
                    <label for="remember_me" class="inline">Remember me</label></td>
            </tr>
            <tr>
                <td colspan='2'>
                    <input name="submit" type="submit" value="Login"/></td>
            </tr>
            <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
        </table>
    </form>
</div>
<div id="footer" th:include="page :: copy"></div>
</body>
</html>

page.html

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
      
  <body>
  
    <div th:fragment="header">
      <a th:href="@{/logout}">Logout</a>
    </div>

    <div>Content goes here</div>
  
    <div th:fragment="copy">Copyright &copy; Craig Walls</div>
  </body>
  
</html>

修改SecurityConfig类的configure(HttpSecurity)方法, 设置自定义的登录页面

package test;

import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl;

import java.util.Objects;


@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/home", "/", "/login").permitAll()
                .antMatchers("/admin").hasAuthority("ROLE_ADMIN")
                .anyRequest().authenticated()
                .and()
                //设置自定义登录页面
                .formLogin().loginPage("/login").and()
                //设置退出后重定向页面
                .logout().logoutSuccessUrl("/").and()
                //启用http basic认证
                .httpBasic().and()
                //启用remember me 功能,   会在浏览器的cookie中增加一个token, 该token包含用户名、密码、过期时间和一个私钥
                .rememberMe()
                .tokenRepository(new InMemoryTokenRepositoryImpl())
                .tokenValiditySeconds(2419200)
                .key("test");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.userDetailsService(new UserDetailServiceImpl()).passwordEncoder(new ReversePasswordEncoder());
    }

    private static class ReversePasswordEncoder implements PasswordEncoder {

        @Override
        public String encode(CharSequence rawPassword) {
            return StringUtils.reverse(String.valueOf(rawPassword));
        }

        @Override
        public boolean matches(CharSequence rawPassword, String encodedPassword) {
            return Objects.equals(encode(rawPassword), encodedPassword);
        }
    }

}

重启程序,访问没有权限的页面时会跳转到自定义的登录页面

posted @ 2020-01-01 16:24  笪笠  阅读(297)  评论(0编辑  收藏  举报