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

所需jar包


UTF-8
UTF-8
1.8
1.8
1.8
3.1.0
3.2.0.RELEASE
4.0.7.RELEASE
2.1.3.RELEASE




org.springframework
spring-webmvc
${springVersion}


org.springframework
spring-jdbc
${springVersion}


org.springframework.security
spring-security-config
${springSecurityVersion}


org.springframework.security
spring-security-web
${springSecurityVersion}




org.thymeleaf
thymeleaf-spring4
${thymeleafVersion}


org.thymeleaf.extras
thymeleaf-extras-springsecurity3
2.1.1.RELEASE



javax.servlet
javax.servlet-api
${servletApiVersion}


org.apache.commons
commons-lang3
3.9

一  启用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>Hometitle>
  head>
  <body>
    Home Page
  body>
html>

authenticate.html

DOCTYPE html>
<html lang="en">
<head>
    <title>Titletitle>
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>Admintitle>
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-logintitle>
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 melabel>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}">Logouta>
    div>

    <div>Content goes herediv>
  
    <div th:fragment="copy">Copyright © Craig Wallsdiv>
  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);
        }
    }

}

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

你可能感兴趣的:(Spring in action读书笔记(七) Spring Security启用权限管理实践)