尚筹网-9.SpringSecurity

1. 权限管理过程中的相关概念

1.1 主体

英文单词:principal

使用系统的用户或设备或从其他系统远程登录的用户等等。简单说就是谁使用系统谁就是主体。

1.2 认证

英文单词:authentication

权限管理系统确认一个主体的身份,允许主体进入系统。简单说就是“主体”证明自己是谁。

笼统的认为就是以前所做的登录操作。

1.3 授权

英文单词:authorization

将操作系统的“权力”“授予”“主体”,这样主体就具备了操作系统中特定功能的能力。

所以简单来说,授权就是给用户分配权限。

尚筹网-9.SpringSecurity_第1张图片

2. 权限管理的主流框架

2.1 SpringSecurity

Spring技术栈的组成部分。

尚筹网-9.SpringSecurity_第2张图片

通过提供完整可扩展的认证和授权支持保护你的应用程序。

官方网址

SpringSecurity特点:

  • 和Spring无缝整合。
  • 全面的权限控制。
  • 专门为Web开发而设计。
    • 旧版本不能脱离Web环境使用。
    • 新版本对整个框架进行了分层抽取,分成了核心模块和Web模块。单独引入核心模块就可以脱离Web环境。
  • 重量级。

2.2 Shiro

Apache旗下的轻量级权限控制框架。

尚筹网-9.SpringSecurity_第3张图片

特点:

  • 轻量级。Shiro主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。
  • 通用性。
    • 好处:不局限于Web环境,可以脱离Web环境使用。
    • 缺陷:在Web环境下一些特定的需求需要手动编写代码定制。

官网网址

3. 使用配置类代替XML配置文件

3.1 @Configuration注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component//由于当前注解带有@Component注解,所以标记当前注解的类可以享受包的自动扫描
public @interface Configuration {
    /**
     * Explicitly specify the name of the Spring bean definition associated
     * with this Configuration class.  If left unspecified (the common case),
     * a bean name will be automatically generated.
     *
     * 

The custom name applies only if the Configuration class is picked up via * component scanning or supplied directly to a {@link AnnotationConfigApplicationContext}. * If the Configuration class is registered as a traditional XML bean definition, * the name/id of the bean element will take precedence. * * @return the specified bean name, if any * @see org.springframework.beans.factory.support.DefaultBeanNameGenerator */ String value() default ""; }

类标记了这个注解就可以使用这个类代替Spring的XML配置文件。

3.2 @Bean注解

用来代替XML配置文件中的bean标签。下面两种形式效果一致:


    

@Configuration
public class AnnotaionConfig{
    @Bean
    public EmpHandler getEmpHandler(){
        return new EmpHandler();
    }
}

提示:Spring通过调用标记了@Bean注解的方法将对象放入IOC容器行为不会重复调用方法。原因是Spring想要获取bean对应的实例对象时会查看IOC容器中是否已经有了这个对象,如果有则不会执行这个方法,从而保证这个bean是单一实例的。

如果希望对应的bean是多实例的,则可以配合@Scope注解。

4. HelloWorld工程创建步骤

4.1 创建Maven的Web工程

4.2 加入SpringMVC环境需要的依赖


    
        org.springframework
        spring-webmvc
        4.3.20.RELEASE
    
    
    
        javax.servlet
        servlet-api
        2.5
        provided
    
    
    
        javax.servlet.jsp
        jsp-api
        2.1.3-b06
        provided
    

4.3 创建SpringMVC配置文件



    
    
        
        
    
    
    

4.4 在web.xml中配置DispatcherServlet



    springDispatcherServlet
    org.springframework.web.servlet.DispatcherServlet
    
        contextConfigLocation
        classpath:spring-mvc.xml
    
    1



    springDispatcherServlet
    /

4.5 创建包

com.atguigu.security.controller

4.6 从例子工程中复制Controller

尚筹网-9.SpringSecurity_第4张图片

4.7 加入webapp目录下文件

尚筹网-9.SpringSecurity_第5张图片

5 在HelloWorld基础上加入SpringSecurity

5.1 加入SpringSecurity依赖



    org.springframework.security
    spring-security-web
    4.2.10.RELEASE



    org.springframework.security
    spring-security-config
    4.2.10.RELEASE



    org.springframework.security
    spring-security-taglibs
    4.2.10.RELEASE

5.2 加入SpringSecurity控制权限的Filter

SpringSecurity使用的是过滤器Filter而不是拦截器Interceptor,意味着SpringSecurity能够管理的不仅仅是SpringMVC中的handler请求,还包含Web应用中所有请求。比如:项目中的静态资源也会被拦截,从而进行权限控制。


    springSecurityFilterChain
    org.springframework.web.filter.DelegatingFilterProxy


    springSecurityFilterChain
    /*

特别注意springSecurityFilterChain标签中必须是springSecurityFilterChain。因为springSecurityFilterChain在IOC容器中对应真正执行权限控制的二十几个Filter,只有叫这个名字才能够加载到这些Filter。

5.3 加入配置类

com.rgh.security.config.WebAppSecurityConfig

@Configuration
@EnableWebSecurity
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {

}

Enable理解为启用。

@EnableWebSecurity注解表示启用Web安全功能。

以后会接触到很多@EnableXxx注解,用来启用对应的功能。

5.4 效果

  • 所有请求都被SpringSecurity拦截,要求登录才可以访问。
  • 静态资源也都被拦截,要求登录。
  • 登录失败有错误提示。

6 SpringSecurity操作实验

下面的操作都是在HelloWorld的基础上逐步增加权限控制设置,循序渐进学习SpringSecurity用法.

6.1 实验1:放行首页和静态资源

重写父类的configure(HttpSecurity security)方法。

protected void configure(HttpSecurity security) throws Exception {
    logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
    security
        .authorizeRequests()
        .anyRequest().authenticated()   //所有请求都需要进行认证
        .and()
        .formLogin()
        .and()
        .httpBasic();
}

重写后

@Override
protected void configure(HttpSecurity security) throws Exception {
    //super.configure(security); 注释掉将取消父类方法中的默认规则
    security.authorizeRequests()        //对请求进行授权
        .antMatchers("/layui/**","/index.jsp")   //使用ANT风格设置要授权的URL地址
        .permitAll()                //允许上面使用ANT风格设置的全部请求
        .anyRequest()               //其他未设置的全部请求
        .authenticated();           //需要认证
}

效果:未授权的请求会跳转到403错误页面。

尚筹网-9.SpringSecurity_第6张图片

6.2 实验2:未授权请求跳转到登录页

@Override
protected void configure(HttpSecurity security) throws Exception {
    //super.configure(security); 注释掉将取消父类方法中的默认规则
    security.authorizeRequests()        //对请求进行授权
        .antMatchers("/layui/**","/index.jsp")   //使用ANT风格设置要授权的URL地址
        .permitAll()                //允许上面使用ANT风格设置的全部请求
        .anyRequest()               //其他未设置的全部请求
        .authenticated()            //需要认证
        .and()
        .formLogin()                //设置未授权请求跳转到登录页面
        .loginPage("/index.jsp")      //指定登录页
        .permitAll();               //为登录页设置所有人都可以访问
}

指定登录页前后SpringSecurity登录地址变化:

登陆指定前/后 地址栏
指定前 /login GET - the login form
/login POST - process the credentials and if valid authenticate the user
/login?error GET - redirect here for failed authentication attempts
/login?logout GET - redirect here after successfully logging out
指定后 /index.jsp GET - the login form
/index.jsp POST - process the credentials and if valid authenticate the user
/index.jsp?error GET - redirect here for failed authentication attempts
/index.jsp?logout GET - redirect here after successfully logging out
尚筹网-9.SpringSecurity_第7张图片

通过调用loginProcessingUrl()方法指定登录地址。

6.3 实验3:设置登录系统的账号、密码

6.3.1 页面设置

给index.jsp设置表单

${SPRING_SECURITY_LAST_EXCEPTION.message}

……

※提示:如果希望在指定登录页面地址后指定登录操作本身地址,可以调用loginProcessingUrl("登录地址").permitAll()方法。

账号、密码的请求参数名

  • 默认账号的请求参数名:username
  • 默认密码的请求参数名:password

要么修改页面上的表单项的name属性值,要么修改配置。如果修改配置可以调用usernameParameter()和passwordParameter()方法。

6.3.2 后端配置

设置登录成功后默认前往的页面

@Override
protected void configure(HttpSecurity security) throws Exception {
    //super.configure(security); 注释掉将取消父类方法中的默认规则
    security.authorizeRequests()        //对请求进行授权
        .antMatchers("/layui/**","/index.jsp")   //使用ANT风格设置要授权的URL地址
        .permitAll()                //允许上面使用ANT风格设置的全部请求
        .anyRequest()               //其他未设置的全部请求
        .authenticated()            //需要认证
        .and()
        .formLogin()                //设置未授权请求跳转到登录页面:开启表单登录功能
        .loginPage("/index.jsp")    //指定登录页
        .permitAll()                //为登录页设置所有人都可以访问
        .defaultSuccessUrl("/main.html"); //设置登录成功后默认前往的URL地址
}

重新另外一个父类的方法,来设置登录系统的账号密码

@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
    //super.configure(auth); 一定要禁用默认规则
    builder.inMemoryAuthentication()
        .withUser("tom").password("123123")  //设置账号密码
        .roles("ADMIN")                      //设置角色
        .and()
        .withUser("jerry").password("456456")//设置另一个账号密码
        .authorities("SAVE","EDIT");         //设置权限
}

Cannot pass a null GrantedAuthority collection问题是由于没有设置roles()或authorities()方法导致的。

6.3.3 ※了解:_csrf如何防止跨站请求伪造?

Cross-site request forgery跨站请求伪造

发送登录请求时没有携带_csrf值,则返回下面错误:

尚筹网-9.SpringSecurity_第8张图片

从钓鱼网站的页面提交的请求无法携带正确、被承认的令牌。

尚筹网-9.SpringSecurity_第9张图片

面试相关问题:在单点登录系统中,认证中心根据浏览器的Cookie识别用户身份。那如果用户的Cookie被劫持仿冒用户身份登录系统怎么办?

除了Cookie之外,还使用_csrf生成的token防止跨站请求伪造。

最后:登录成功后具体资源都可以访问了。

6.4 实验4:用户注销

通过调用HttpSecurity对象的一系列方法设置注销功能。

logout()方法:开启注销功能

logoutUrl()方法:自定义注销功能的URL地址

尚筹网-9.SpringSecurity_第10张图片

如果CSRF功能没有禁用,那么退出请求必须是POST方式。如果禁用了CSRF功能则任何请求方式都可以。

logoutSuccessUrl()方法:退出成功后前往的URL地址

addLogoutHandler()方法:添加退出处理器

logoutSuccessHandler()方法:退出成功处理器

退出的表单

退出

所属类:WebAppSecurityConfig

@Override
protected void configure(HttpSecurity security) throws Exception {
    security
        .authorizeRequests()    // 对请求进行授权
        .antMatchers("/index.jsp","/layui/**")  // 设置要进行授权的请求地址
        .permitAll()            // antMatchers()匹配的请求直接放行
        .anyRequest()           // 除了antMatchers()匹配的其他请求
        .authenticated()        // 需要“认证”后才可以访问
        .and()
        .formLogin()            // 指定使用表单作为登录方式
        .loginPage("/index.jsp")    // 指定登录页地址
        .permitAll()
        .defaultSuccessUrl("/main.html")    // 登录成功后访问的地址
        .usernameParameter("loginacct")     // 告诉SpringSecurity登录账号的请求参数名
        .passwordParameter("credential")    // 告诉SpringSecurity登录密码的请求参数名
        .and()
        .logout()                           // 开启用户“退出登录”功能
        .logoutUrl("/my/app/logout")        // 设置退出登录URL地址
        .logoutSuccessUrl("/index.jsp");    // 成功退出登录后前往的地址
}

6.5 实验5:基于角色进行访问控制

所属类:WebAppSecurityConfig

通过HttpSecurity对象设置资源的角色要求

@Override
protected void configure(HttpSecurity security)throws Exception {
    security.authorizeRequests()        //对请求进行授权
        .antMatchers("/layui/**","/index.jsp")   //使用ANT风格设置要授权的URL地址
        .permitAll()                //允许上面使用ANT风格设置的全部请求
        .antMatchers("/level1/**")
        .hasRole("学徒")
        .antMatchers("/level2/**")
        .hasRole("大师")
        .antMatchers("/level3/**")
        .hasRole("宗师")
        .anyRequest()               //其他未设置的全部请求
        .authenticated()            //需要认证
        .and()
        .formLogin()                //设置未授权请求跳转到登录页面:开启表单登录功能
        .loginPage("/index.jsp")    //指定登录页
        .permitAll()                //为登录页设置所有人都可以访问
        .defaultSuccessUrl("/main.html") //设置登录成功后默认前往的URL地址
        .and()
        .logout()
        .logoutUrl("/my/logout")
        .logoutSuccessUrl("/index.jsp");
}

通过AuthenticationManagerBuilder对象设置用户登录时具备的角色

@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
    builder.inMemoryAuthentication()
        .withUser("tom").password("123123")  //设置账号密码
        .roles("ADMIN","学徒","宗师")           //设置角色
        .and()
        .withUser("jerry").password("456456")//设置另一个账号密码
        .authorities("SAVE","EDIT");         //设置权限
}

访问被拒绝后看到403错误页面:

尚筹网-9.SpringSecurity_第11张图片

注意:调用顺序

.antMatchers("/level1/**")                  //设置匹配/level1/**的地址
.hasRole("学徒")                              //要求具备“学徒”角色
.antMatchers("/level2/**")
.hasRole("大师")
.antMatchers("/level3/**")
.hasRole("宗师")
.anyRequest()                               //其实未设置的所有请求
.authenticated()                            //需要认证才可以访问

蓝色代码设置范围更大

红色代码设置范围相对小

如果蓝色代码先调用,会把后面红色代码的设置覆盖,导致红色代码无效。所以要先做具体小范围设置,再做大范围模糊设置。

6.6 实验6:自定义403错误页面

由main.jsp复制得到no_auth.jsp。修改如下:

抱歉!您没有权限访问此功能!

前往自定义页面方式一:

所在类:AdminController

@RequestMapping("/to/no/auth/page")
public String toNoAuthPage() {
    return "no_auth";
}

所在类:WebAppSecurityConfig

方法:

@Override
protected void configure(HttpSecurity security)

HttpSecurity对象.exceptionHandling().accessDeniedPage("/to/no/auth/page");

.logoutSuccessUrl("/index.jsp")     // 成功退出登录后前往的地址
.and()
.exceptionHandling()
.accessDeniedPage("/to/no/auth/page.html")  // 访问被拒绝后前往的页面
;

前往自定义页面方式二:

HttpSecurity对象.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {


@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
                   AccessDeniedException accessDeniedException) throws IOException, ServletException {
    request.setAttribute("message", accessDeniedException.getMessage());
    request.getRequestDispatcher("/WEB-INF/views/no_auth.jsp").forward(request, response);
}
});

6.7 实验7:记住我-内存版

HttpSecurity对象调用rememberMe()方法。

登录表单携带名为remember-me的请求参数。具体做法是将登录表单中的checkbox的name设置为remember-me


如果不能使用“remember-me”作为请求参数名称,可以使用rememberMeParameter()方法定制。

原理简要分析:

尚筹网-9.SpringSecurity_第12张图片

通过开发者工具看到浏览器端存储了名为remember-me的Cookie。根据这个Cookie的value在服务器端找到以前登录的User。

而且这个Cookie被设置为存储2个星期的时间。

尚筹网-9.SpringSecurity_第13张图片

6.8 实验8:记住我-数据库版

为了让服务器重启也不影响记住登录状态,将用户登录状态信息存入数据库。

6.8.1 建立数据库连接

依赖



    com.alibaba
    druid
    1.1.12




    mysql
    mysql-connector-java
    5.1.47


    org.springframework
    spring-orm
    4.3.20.RELEASE

spring-mvc.xml:配置数据源



    
    
    
    



    

创建数据库

CREATE DATABASE `security` CHARACTER SET utf8;

在WebAppSecurityConfig类中注入数据源

@Autowired
private DataSource dataSource;

6.8.2 启用令牌仓库功能

尚筹网-9.SpringSecurity_第14张图片

所在类:WebAppSecurityConfig

方法:

@Override
protected void configure(HttpSecurity security){
    JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
    repository.setDataSource(dataSource);
    
    security.
        ------
        .accessDeniedPage("/to/no/auth/page.html")  // 访问被拒绝后前往的页面
        .and()
        .rememberMe()       // 开启记住我功能
        .tokenRepository(repository)    // 指定token库
        ;
}

HttpSecurity对象.tokenRepository(repository);

注意:需要进入JdbcTokenRepositoryImpl 类中找到创建persistent_logins表的SQL语句创建persistent_logins表。

CREATE TABLE persistent_logins (
    username VARCHAR (64) NOT NULL,
    series VARCHAR (64) PRIMARY KEY,
    token VARCHAR (64) NOT NULL,
    last_used TIMESTAMP NOT NULL
);

6.9 实验9:查询数据库完成认证

6.9.1 了解:SpringSecurity默认实现

builder.jdbcAuthentication().usersByUsernameQuery("tom");

在usersByUsernameQuery("tom")等方法中最终调用JdbcDaoImpl类的方法查询数据库。

尚筹网-9.SpringSecurity_第15张图片

SpringSecurity的默认实现已经将SQL语句硬编码在了JdbcDaoImpl类中。这种情况下,我们有下面三种选择:

  • 按照JdbcDaoImpl类中SQL语句设计表结构。
  • 修改JdbcDaoImpl类的源码。
  • 不使用jdbcAuthentication()。

6.9.2 自定义数据库查询方式

builder.userDetailsService(userDetailsService)

其中userDetailsService需要自定义实现UserDetailsService接口的类并自动装配。

所在类:AppUserDetailService

@Service
public class AppUserDetailService implements UserDetailsService {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1.使用SQL语句根据用户名查询用户对象
        String sql = "SELECT id,loginacct,userpswd,username,email,createtime FROM t_admin WHERE loginacct = ?";
        // 2.获取查询结果
        Map resultMap = jdbcTemplate.queryForMap(sql, username);
        // 3.获取用户名、密码数据
        String loginacct = resultMap.get("loginacct").toString();
        String userpswd = resultMap.get("userpswd").toString();
        // 4.创建权限列表
        List list = AuthorityUtils.createAuthorityList("ROLE_学徒","ROLE_大师");
        return new User(loginacct, userpswd, list);
    }
}
create table t_admin
(
    id                   int not null auto_increment,
    loginacct            varchar(255) not null,
    userpswd             char(32) not null,
    username             varchar(255) not null,
    email                varchar(255) not null,
    createtime           char(19),
    primary key (id)
);

6.9.3 使用自定义UserDetailsService完成登录

所在类:WebAppSecurityConfig

方法:

@Autowired
private AppUserDetailService userDetailService;
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
    //      builder
    //          .inMemoryAuthentication()
    //          .withUser("tom")            // 指定登录系统的账号
    //          .password("123123")         // 指定账号对应的密码
    //          .roles("大师")        // 必须设置角色或权限,否则会出现Cannot pass a null GrantedAuthority collection错误
    //          ;
    builder.userDetailsService(userDetailService);
}

6.9.4 “ROLE_”前缀问题

尚筹网-9.SpringSecurity_第16张图片

在自定义的UserDetailsService中,使用org.springframework.security.core.authority.AuthorityUtils.createAuthorityList(String...)工具方法获取创建SimpleGrantedAuthority对象添加角色时需要手动在角色名称前加“ROLE_”前缀。

6.10 实验10:应用自定义密码加密规则

自定义类实现org.springframework.security.crypto.password.PasswordEncoder(使用没有过时的)接口。

所在类:PasswordEncoderService

@Service
public class PasswordEncoderService implements PasswordEncoder {
    // 对原始明文密码进行加密
    @Override
    public String encode(CharSequence rawPassword) {
        Assert.notNull(rawPassword, "rawPassword can not be null!");
        String password = CrowdFundingUtils.md5(rawPassword.toString());
        return password;
    }
    // 将明文密码和密文密码进行比较
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        Assert.notNull(rawPassword, "rawPassword can not be null!");
        String password = CrowdFundingUtils.md5(rawPassword.toString());
        return Objects.equals(password, encodedPassword);
    }
}
  • encode()方法对明文进行加密。
  • matches()方法对明文加密后和密文进行比较。

在配置类中的configure(AuthenticationManagerBuilder)方法中应用自定义密码加密规则

所在类:WebAppSecurityConfig

@Autowired
private PasswordEncoder passwordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
    builder.userDetailsService(userDetailService).passwordEncoder(passwordEncoder);
}

※SpringSecurity提供的BCryptPasswordEncoder加密规则。

BCryptPasswordEncoder创建对象后代替自定义passwordEncoder对象即可。BCryptPasswordEncoder在加密时通过加入随机盐值让每一次的加密结果都不同。能够避免密码的明文被猜到。

而在对明文和密文进行比较时,BCryptPasswordEncoder会在密文的固定位置取出盐值,重新进行加密。

尚筹网-9.SpringSecurity_第17张图片

所在类:BCryptPasswordEncoderTest

代码:

public class BCryptPasswordEncoderTest {
    public static void main(String[] args) {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        CharSequence rawPassword = "123123";
        for(int i = 0; i < 10; i++) {
            String encodedPassword = encoder.encode(rawPassword);
            System.out.println(encodedPassword);
        }
        System.out.println();
        boolean matches = encoder.matches(rawPassword, "$2a$10$Y2Cq8ilT21ME.lvu6bwcPO/RMkU7ucAZpmFzx7GDTXK9KNxHyEM1e");
        System.out.println(matches);
    }
}

7 众筹项目加入SpringSecurity环境

尚筹网-9.SpringSecurity_第18张图片

7.1 加入依赖


    org.springframework.security
    spring-security-web
    4.2.10.RELEASE


    org.springframework.security
    spring-security-config
    4.2.10.RELEASE


    org.springframework.security
    spring-security-taglibs
    4.2.10.RELEASE

7.2 Filter

web.xml



    springSecurityFilterChain
    org.springframework.web.filter.DelegatingFilterProxy


    springSecurityFilterChain
    /*

7.3 配置类

CrowdfundingSecurityConfig

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class CrowdfundingSecurityConfig extends WebSecurityConfigurerAdapter {

}

@EnableGlobalMethodSecurity(prePostEnabled=true)注解表示启用全局方法权限管理功能。

7.4 自动扫描的包

考虑到权限控制系统更多的需要控制Web请求,而且有些请求没有经过Service方法,所以在SpringMVC的IOC容器中扫描CrowdfundingSecurityConfig。但是,SpringSecurity是有管理Service、Dao方法的能力的。

配置文件:/atcrowdfunding-admin-1-webui/src/main/resources/spring-web-mvc.xml


7.5 多个IOC容器之间的关系

问题描述:项目启动时控制台抛异常说找不到“springSecurityFilterChain”的bean。

org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'springSecurityFilterChain' is defined

问题分析:

Web组件加载顺序:Listener→Filter→Servlet

  • Spring IOC容器:ContextLoaderListener创建
  • SpringMVC IOC容器:DispatcherServlet创建
  • springSecurityFilterChain:从IOC容器中找到对应的bean

ContextLoaderListener初始化后,springSecurityFilterChain就在ContextLoaderListener创建的IOC容器中查找所需要的bean,但是我们没有在ContextLoaderListener的IOC容器中扫描SpringSecurity的配置类,所以springSecurityFilterChain对应的bean找不到。

尚筹网-9.SpringSecurity_第19张图片

问题解决:

将ContextLoaderListener取消,原本由ContextLoaderListener读取的Spring配置文件交给DispatcherServlet负责读取。











    springDispatcherServlet
    org.springframework.web.servlet.DispatcherServlet
    
        contextConfigLocation
        classpath:spring-web-mvc.xml,classpath:spring-persist-*.xml
    
    1



    springDispatcherServlet
    
    *.html
    *.json

7.6 SpringSecurity初始设置

放行首页、静态资源。

@Override
protected void configure(HttpSecurity security) throws Exception {
    security.authorizeRequests()
        .antMatchers("/index.html","/bootstrap/**","/css/**","/fonts/**","/img/**","/jquery/**","/layer/**","/script/**","/ztree/**")
        .permitAll()
        .anyRequest()
        .authenticated();
}

8 登录

8.1 SpringSecurity开启表单登录功能并前往登录表单页面

.formLogin()
.loginPage("/admin/to/login/page.html")
.permitAll()

8.2 循环重定向问题

尚筹网-9.SpringSecurity_第20张图片

去登录页面和登录请求本身都需要permitAll()否则登录和去登录页面本身都需要登录,形成死循环。

8.3 提交登录表单

注意:我们以前自己写的登录handler方法以后就不使用了。使用SpringSecurity之后,登录请求由SpringSecurity处理。

security
    .authorizeRequests()
    .antMatchers("/index.html")
    .permitAll()
    .antMatchers("/bootstrap/**")
    .permitAll()
    .antMatchers("/css/**")
    .permitAll()
    .antMatchers("/fonts/**")
    .permitAll()
    .antMatchers("/img/**")
    .permitAll()
    .antMatchers("/jquery/**")
    .permitAll()
    .antMatchers("/layer/**")
    .permitAll()
    .antMatchers("/script/**")
    .permitAll()
    .antMatchers("/ztree/**")
    .permitAll()
    .anyRequest()
    .authenticated()
    .and()
    .formLogin()
    .loginPage("/admin/to/login/page.html")
    .permitAll()
    .loginProcessingUrl("/admin/security/login.html")
    .permitAll()
    .usernameParameter("loginacct")
    .passwordParameter("userpswd")
    .defaultSuccessUrl("/admin/to/main/page.html")
    .and()
    .logout()
    .logoutUrl("/admin/security/logout.html")
    .logoutSuccessUrl("/index.html")
    .and()
    .csrf()
    .disable(); // 禁用CSRF功能
//禁用CSRF功能。注意:这仅仅是我们学习过程中偷懒的做法,实际开发时还是不要禁用。
security.csrf().disable();

8.4 登录操作查询相关数据的SQL

所在类:CrowdFundingUserDetailsService

// 1.根据用户名从数据库查询Admin对象
AdminExample adminExample = new AdminExample();
adminExample
    .createCriteria()
    .andLoginacctEqualTo(username);
List adminList = adminMapper.selectByExample(adminExample);
List roleList = roleMapper.selectAssignRoleList(adminId);
  

8.5 SecurityAdmin封装

/**
 * 扩展User类
 * 创建SecurityAdmin对象时调用构造器,传入originalAdmin和authorities
 * 可以通过getOriginalAdmin()方法获取原始Admin对象
 *
 */
public class SecurityAdmin extends User {
    private static final long serialVersionUID = 1L;
    private Admin originalAdmin;
    public SecurityAdmin(Admin originalAdmin, Collection authorities) {
        super(originalAdmin.getLoginAcct(), originalAdmin.getUserPswd(), authorities);
        this.originalAdmin = originalAdmin;
    }
    public Admin getOriginalAdmin() {
        return originalAdmin;
    }
}

8.6 loadUserByUsername(String username)方法

所在类:CrowdFundingUserDetailsService

@Service
public class CrowdFundingUserDetailsService implements UserDetailsService {
    @Autowired
    private AdminMapper adminMapper;
    @Autowired
    private RoleMapper roleMapper;
    @Autowired
    private AuthMapper authMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1.根据用户名从数据库查询Admin对象
        AdminExample adminExample = new AdminExample();
        adminExample
            .createCriteria()
            .andLoginacctEqualTo(username);
        List adminList = adminMapper.selectByExample(adminExample);
        if(adminList == null || adminList.size() != 1) {
            return null;
        }
        Admin admin = adminList.get(0);
        // 2.获取数据库中密码
        // String userpswd = admin.getUserpswd();
        // 3.查询Admin对应的权限信息(包括角色、权限)
        Integer adminId = admin.getId();
        // ①创建集合用来存放权限信息
        Collection authorities = new ArrayList<>();
        // ②根据adminId查询对应的角色
        List roleList = roleMapper.selectAssignRoleList(adminId);
        for (Role role : roleList) {
            String roleName = role.getName();
            // 注意:一定要加“ROLE_”
            authorities.add(new SimpleGrantedAuthority("ROLE_"+roleName));
        }
        // ③根据adminId查询对应的权限
        List authNameList = authMapper.selectAssignedAuthList(adminId);
        for (String authName : authNameList) {
            authorities.add(new SimpleGrantedAuthority(authName));
        }
        // 4.封装到User的子类SecurityAdmin类型的对象中
        // User user = new User(username, userpswd, authorities );
        SecurityAdmin securityAdmin = new SecurityAdmin(admin, authorities);
        return securityAdmin;
    }
}

9 认证功能问题调整

9.1 取消手动进行登录检查的拦截器

Spring MVC配置文件

/atcrowdfunding-1-ui/src/main/resources/spring-web.xml




9.2 登录成功后显示实际登录的用户名

所在文件:include-nav.jsp

第一步:导入SpringSecurity标签库

<%@ taglib uri="http://www.springframework.org/security/tags" prefix="security" %>

第二步:使用security:authentication标签



9.3 加入关联关系假数据

页面操作或者直接将数据插入到数据库中即可。

9.4 保存Admin时使用SpringSecurity加密方式

所在类:AdminServiceImpl

@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void saveAdmin(Admin admin) {
    //对密码进行加密
    String userPswd = admin.getUserPswd();
    userPswd = passwordEncoder.encode(userPswd);
    admin.setUserPswd(userPswd);
    //执行保存
    adminMapper.insert(admin);
}

10 权限控制

10.1 handler方法的权限控制

  • linda:
    • ROLE_总裁
    • role:get
  • peiqi:
    • ROLE_经理
    • user:get

需要进行权限控制的handler方法

全类名:com.atguigu.crowd.handler.AdminHandler

@PreAuthorize(value="hasRole('PM - 项目经理')")
@RequestMapping("/admin/query")
public String queryWithSearch(
    @RequestParam(value="keyword", defaultValue="") String keyword,
    @RequestParam(value="pageNo", defaultValue="1") int pageNo,
    Model model
) {
    // 1.调用Service方法获取分页数据
    PageInfo pageInfo = adminService.getAdminPageInfoWithKeyword(keyword, pageNo, ArgumentsConstant.PAGE_SIZE);
    // 2.将分页数据存入模型
    model.addAttribute(AttrNameConstant.PAGE, pageInfo);
    // 3.跳转页面
    return "admin_page";
}

注意:@PreAuthorize注解生效需要@EnableGlobalMethodSecurity(prePostEnabled=true)注解支持。

10.2 使用全局配置控制

所在类:CrowdfundingSecurityConfig

.antMatchers("/admin/query/for/search.html")
    .hasRole("董事长")
    ……
    .and()
    .exceptionHandling()
    .accessDeniedHandler(new CrowdFundingAccessDeniedHandler())

accessDeniedHandler()方法指定了检测到权限不匹配时的处理方式。

新建类实现AccessDeniedHandler接口

public class CrowdFundingAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        request.setAttribute("exception",e);
        request.getRequestDispatcher("/WEB-INF/system-error.jsp").forward(request,response);
    }
}

10.3 页面元素权限控制

页面:admin-main.jsp

使用SpringSecurity提供的标签可以详细对页面元素进行权限控制。

第一步:导入标签库

<%@ taglib uri="http://www.springframework.org/security/tags" prefix="security" %>

第二步:使用security:authorize标签


    
        
    

你可能感兴趣的:(尚筹网-9.SpringSecurity)