day09【后台】权限控制-下

1、目标5:密码加密

1.1、修改数据库表结构

  • 由于之前使用MD5加密,密码字段长度为32位;现采用带盐值的加密,需要修改密码字段的长度

access control 门禁管理系统初始账户密码_Spring

  • 顺带把密码也改了

access control 门禁管理系统初始账户密码_Spring_02

1.2、注入BCryptPasswordEncoder对象

  • 注 意 : 如果在SpringSecurity的配置类中用@Bean注解将BCryptPasswordEncoder对象存入 IOC 容器, 那么 Service 组件将获取不到BCryptPasswordEncoder对象。儿子可以用他老爹的东西,但他老爹并不能用他儿子的东西。。。

access control 门禁管理系统初始账户密码_Mybatis_03

  • 我们需要在Spring的配置文件中注入BCryptPasswordEncoder对象,这样SpringMVC IOC容器也能使用该对象

access control 门禁管理系统初始账户密码_权限控制_04

<!-- 配置BCryptPasswordEncoder的bean -->
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

1.3、开启加密功能

  • SpringSecurity的配置类中注入BCryptPasswordEncoder对象,并重写configure方法
@Autowired
private BCryptPasswordEncoder passwordEncoder;

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

    // 临时使用内存版登录的模式测试代码
    /*
		builder
			.inMemoryAuthentication()
			.withUser("Heygo")
			.password("123123")
			.roles("ADMIN");
		*/

    builder
        .userDetailsService(userDetailsService)
        .passwordEncoder(passwordEncoder);

}

1.4、管理员密码加密

  • AdminService中,注入BCryptPasswordEncoder对象,并使用其对密码进行加密

access control 门禁管理系统初始账户密码_权限控制_05

@Autowired
private BCryptPasswordEncoder passwordEncoder;

@Override
public void saveAdmin(Admin admin) {	
    // 1.密码加密
    String userPswd = admin.getUserPswd();
    /* 之前的加密方式
		userPswd = CrowdUtil.md5(userPswd);
		*/
    userPswd = passwordEncoder.encode(userPswd);
    admin.setUserPswd(userPswd);

    // 2.生成创建时间
    Date date = new Date();
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String createTime = format.format(date);
    admin.setCreateTime(createTime);

    // 3.执行保存
    try {
        adminMapper.insert(admin);
    } catch (Exception e) {
        e.printStackTrace();			
        logger.info("异常全类名="+e.getClass().getName());			
        if(e instanceof DuplicateKeyException) {
            throw new LoginAcctAlreadyInUseException(CrowdConstant.MESSAGE_LOGIN_ACCT_ALREADY_IN_USE);
        }
    }		
}

1.5、实验测试

  • Heygo能够正常登陆

access control 门禁管理系统初始账户密码_Spring_06

  • 创建管理员NiuNiu,密码带盐值哟

access control 门禁管理系统初始账户密码_SpringSecurity_07

access control 门禁管理系统初始账户密码_SpringSecurity_08

2、目标 6: 显示用户昵称

2.1、修改顶部navbar

2.1.1、导入SpringSecurity标签库
  • include-nav.jsp顶部导航栏中,引入SpringSecurity标签库

access control 门禁管理系统初始账户密码_Mybatis_09

<%@ taglib uri="http://www.springframework.org/security/tags" prefix="security" %>
2.1.2、获取登录用户的信息
  • 修改获取用户昵称的代码

access control 门禁管理系统初始账户密码_SpringSecurity_10

<security:authentication property="principal.originalAdmin.userName"/>

2.2、分析的过程

  • 分析过程如下

access control 门禁管理系统初始账户密码_SpringSecurity_11

<p>显示出来才发现, principal 原来是我们自己封装的 SecurityAdmin 对象</p>
<p>SpringSecurity 处理完登录操作之后把登录成功的 User 对象以 principal 属性名存入了 UsernamePasswordAuthenticationToken 对象</p>
Principal:<security:authentication property="principal.class.name" />
<br /> 
访 问 SecurityAdmin 对 象 的 属 性 :<security:authentication property="principal.originalAdmin.loginAcct" />
<br /> 
访 问 SecurityAdmin 对 象 的 属 性 :<security:authentication property="principal.originalAdmin.userPswd" />
<br /> 
访 问 SecurityAdmin 对 象 的 属 性 :<security:authentication property="principal.originalAdmin.userName" />
<br /> 
访 问 SecurityAdmin 对 象 的 属 性 :<security:authentication property="principal.originalAdmin.email" />
<br /> 
访 问 SecurityAdmin 对 象 的 属 性 :<security:authentication property="principal.originalAdmin.createTime" />
<br />

2.3、源码分析

  • UsernamePasswordAuthenticationToken类中:这不就是我们编写的SecurityAdmin类吗?

access control 门禁管理系统初始账户密码_SpringSecurity_12

  • Principal:主体,可以理解为登录的用户对象,以下为UsernamePasswordAuthenticationToken类的源码
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {

	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

	// ~ Instance fields
	// ================================================================================================

	private final Object principal;
	private Object credentials;

	// ~ Constructors
	// ===================================================================================================

	/**
	 * This constructor can be safely used by any code that wishes to create a
	 * <code>UsernamePasswordAuthenticationToken</code>, as the {@link #isAuthenticated()}
	 * will return <code>false</code>.
	 *
	 */
	public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
		super(null);
		this.principal = principal;
		this.credentials = credentials;
		setAuthenticated(false);
	}

	/**
	 * This constructor should only be used by <code>AuthenticationManager</code> or
	 * <code>AuthenticationProvider</code> implementations that are satisfied with
	 * producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
	 * authentication token.
	 *
	 * @param principal
	 * @param credentials
	 * @param authorities
	 */
	public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
			Collection<? extends GrantedAuthority> authorities) {
		super(authorities);
		this.principal = principal;
		this.credentials = credentials;
		super.setAuthenticated(true); // must use super, as we override
	}

	// ~ Methods
	// ========================================================================================================

	public Object getCredentials() {
		return this.credentials;
	}

	public Object getPrincipal() {
		return this.principal;
	}

	public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
		if (isAuthenticated) {
			throw new IllegalArgumentException(
					"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
		}

		super.setAuthenticated(false);
	}

	@Override
	public void eraseCredentials() {
		super.eraseCredentials();
		credentials = null;
	}
}

2.4、实验效果

  • 乌拉~

access control 门禁管理系统初始账户密码_Mybatis_13

3、目标 7:密码的擦除

3.1、擦除密码与凭证

  • 在如下打上断点,用户登录时则会触发此断点,将会执行擦除用户凭证的操作

access control 门禁管理系统初始账户密码_SpringSecurity_14

@Override
public void eraseCredentials() {
    super.eraseCredentials();
    credentials = null;
}
  • Step into进入super.eraseCredentials()方法

access control 门禁管理系统初始账户密码_Mybatis_15

public void eraseCredentials() {
    eraseSecret(getCredentials());
    eraseSecret(getPrincipal());
    eraseSecret(details);
}
  • Step into进入eraseSecret(getCredentials())方法:
    if (secret instanceof CredentialsContainer) {条件并不满足

access control 门禁管理系统初始账户密码_Spring_16

private void eraseSecret(Object secret) {
    if (secret instanceof CredentialsContainer) {
        ((CredentialsContainer) secret).eraseCredentials();
    }
}
  • Step into进入eraseSecret(getPrincipal())方法:
    if (secret instanceof CredentialsContainer) {条件成立,并且secret对象的类型就是我们自定义的SecurityAdmin类型

access control 门禁管理系统初始账户密码_权限控制_17

private void eraseSecret(Object secret) {
    if (secret instanceof CredentialsContainer) {
        ((CredentialsContainer) secret).eraseCredentials();
    }
}
  • Step into进入((CredentialsContainer) secret).eraseCredentials()方法:
    password = null:擦除User类中的password,并没有擦除我们自定义类中的密码

access control 门禁管理系统初始账户密码_SpringSecurity_18

public void eraseCredentials() {
    password = null;
}
  • eraseSecret(details)方法:擦除IP地址、SessionId等信息
    因为if (secret instanceof CredentialsContainer) {条件不满足,所以也不会执行

access control 门禁管理系统初始账户密码_SpringSecurity_19

  • 擦除credentials,将其赋值为null

access control 门禁管理系统初始账户密码_SpringMVC_20

  • 结论:
  • 擦除User类中的password属性
  • 擦除credentials对象

3.2、擦除SecurityAdmin的密码

  • 擦除自定义类中的密码字段

access control 门禁管理系统初始账户密码_Spring_21

public class SecurityAdmin extends User {
	
	private static final long serialVersionUID = 1L;
	
	// 原始的Admin对象,包含Admin对象的全部属性
	private Admin originalAdmin;
	
	public SecurityAdmin(
			// 传入原始的Admin对象
			Admin originalAdmin, 
			
			// 创建角色、权限信息的集合
			List<GrantedAuthority> authorities) {
		
		// 调用父类构造器
		super(originalAdmin.getLoginAcct(), originalAdmin.getUserPswd(), authorities);
		
		// 给本类的this.originalAdmin赋值
		this.originalAdmin = originalAdmin;
		
		// 将原始Admin对象中的密码擦除
		this.originalAdmin.setUserPswd(null);
		
	}
	
	// 对外提供的获取原始Admin对象的getXxx()方法
	public Admin getOriginalAdmin() {
		return originalAdmin;
	}

}

3.3、实验效果

  • 页面代码与显示结果:
  • Credentials被擦除
  • User类中的password字段被擦除
  • SecurityAdmin中的usePswd字段被擦除

access control 门禁管理系统初始账户密码_Spring_22

Credentials:<security:authentication property="credentials"/>
<br/>
<p>显示出来才发现, principal 原来是我们自己封装的 SecurityAdmin 对象</p>
<p>SpringSecurity 处理完登录操作之后把登录成功的 User 对象以 principal 属性名存入了 UsernamePasswordAuthenticationToken 对象</p>
Principal:<security:authentication property="principal.class.name" />
<br/>
Principal中的密码:<security:authentication property="principal.password"/>
<br /> 
访 问 SecurityAdmin 对 象 的 loginAcct :<security:authentication property="principal.originalAdmin.loginAcct" />
<br /> 
访 问 SecurityAdmin 对 象 的 userPswd :<security:authentication property="principal.originalAdmin.userPswd" />
<br /> 
访 问 SecurityAdmin 对 象 的 userName :<security:authentication property="principal.originalAdmin.userName" />
<br /> 
访 问 SecurityAdmin 对 象 的 email :<security:authentication property="principal.originalAdmin.email" />
<br /> 
访 问 SecurityAdmin 对 象 的 createTime :<security:authentication property="principal.originalAdmin.createTime" />
<br />

4、目标 8: 权限控制

4.1、整体流程

  • Admin --> Role --> Auth

4.2、修改数据库表

4.2.1、测试数据说明
  • 用户: adminOperator
  • 角色:经理
  • 权限:无
  • 角色:经理操作者
  • 权限:user:save
  • 最终组装后:ROLE经理, ROLE经理操作者, user:save
  • 用户:roleOperator
  • 角色:部长
  • 权限:无
  • 角色:部长操作者
  • 权限:role:delete
  • 最终组装后:ROLE部长, ROLE部长操作者, role:delete
4.2.2、t_admin
  • 添加两个管理员(用户):adminOperatorroleOperator

access control 门禁管理系统初始账户密码_Mybatis_23

4.2.3、t_auth
  • 添加user:save权限

access control 门禁管理系统初始账户密码_权限控制_24

4.2.4、t_role
  • 添加如下四个角色

access control 门禁管理系统初始账户密码_SpringMVC_25

4.3、分配角色与权限

4.3.1、给管理员分配角色

access control 门禁管理系统初始账户密码_Spring_26

access control 门禁管理系统初始账户密码_权限控制_27

4.3.2、给角色分配权限

access control 门禁管理系统初始账户密码_Spring_28

access control 门禁管理系统初始账户密码_权限控制_29

4.4、权限认证:访问管理员分页页面

4.4.1、重写configure方法
  • 重写configure方法:访问管理员分页页面需具备【经理】角色
@Override
protected void configure(HttpSecurity security) throws Exception {

    security
        .authorizeRequests()						// 对请求进行授权
        .antMatchers("/admin/to/login/page.html")	// 针对登录页进行设置
        .permitAll()								// 无条件访问

        // 放行静态资源
        .antMatchers("/bootstrap/**")	
        .permitAll()                    
        .antMatchers("/crowd/**")       
        .permitAll()                    
        .antMatchers("/css/**")         
        .permitAll()                    
        .antMatchers("/fonts/**")       
        .permitAll()                    
        .antMatchers("/img/**")         
        .permitAll()                    
        .antMatchers("/jquery/**")      
        .permitAll()                    
        .antMatchers("/layer/**")       
        .permitAll()                    
        .antMatchers("/script/**")      
        .permitAll()                    
        .antMatchers("/ztree/**")       
        .permitAll()
        .antMatchers("/admin/get/page.html")	// 针对管理员分页页面设定访问控制
        .hasRole("经理")							// 要求具备经理角色
        .anyRequest()					// 其他任意请求
        .authenticated()				// 认证后访问
        .and()
        .csrf()							// 防跨站请求伪造功能
        .disable()						// 禁用
        .formLogin()					// 开启表单登录的功能
        .loginPage("/admin/to/login/page.html")			// 指定登录页面
        .loginProcessingUrl("/security/do/login.html")	// 指定处理登录请求的地址
        .defaultSuccessUrl("/admin/to/main/page.html")	// 指定登录成功后前往的地址
        .usernameParameter("loginAcct")	// 账号的请求参数名称
        .passwordParameter("userPswd")	// 密码的请求参数名称
        .and()
        .logout()						// 开启退出登录功能
        .logoutUrl("/seucrity/do/logout.html")			// 指定退出登录地址
        .logoutSuccessUrl("/admin/to/login/page.html")	// 指定退出成功以后前往的地址
        ;

}
4.4.2、实验效果
  • adminOperator可以访问管理员分页页面

access control 门禁管理系统初始账户密码_SpringSecurity_30

  • roleOperator无法访问管理员分页页面

access control 门禁管理系统初始账户密码_Mybatis_31

4.4.3、关于异常映射的说明
  • 为什么显示403页面?程序执行顺序:DelegatingFilterProxy --> DispatcherServlet

access control 门禁管理系统初始账户密码_Spring_32

  • 如下配置直接将权限验证交由DelegatingFilterProxy管理,也就是说,如果用户权限不足,直接403,根本不会到达DispatcherServlet,访问就被拒绝,那么整个程序不会经过DispatcherServlet,这个403页面是SpringSecurity抛出的
.antMatchers("/admin/get/page.html")		// 针对管理员分页页面设定访问控制
.hasRole("经理")				               // 要求具备经理角色
4.4.4、基于SpringSecurity的异常映射
  • 重写configure方法:DelegatingFilterProxy拦截访问请求后,返回的异常映射页面
@Override
protected void configure(HttpSecurity security) throws Exception {

    security
        .authorizeRequests()						// 对请求进行授权
        .antMatchers("/admin/to/login/page.html")	// 针对登录页进行设置
        .permitAll()								// 无条件访问

        // 放行静态资源
        .antMatchers("/bootstrap/**")	
        .permitAll()                    
        .antMatchers("/crowd/**")       
        .permitAll()                    
        .antMatchers("/css/**")         
        .permitAll()                    
        .antMatchers("/fonts/**")       
        .permitAll()                    
        .antMatchers("/img/**")         
        .permitAll()                    
        .antMatchers("/jquery/**")      
        .permitAll()                    
        .antMatchers("/layer/**")       
        .permitAll()                    
        .antMatchers("/script/**")      
        .permitAll()                    
        .antMatchers("/ztree/**")       
        .permitAll()
        .antMatchers("/admin/get/page.html")	// 针对管理员分页页面设定访问控制
        .hasRole("经理")							// 要求具备经理角色
        .anyRequest()					// 其他任意请求
        .authenticated()				// 认证后访问
        .and()
        .exceptionHandling()
        .accessDeniedHandler(new AccessDeniedHandler() {		
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse response,
                               AccessDeniedException accessDeniedException) throws IOException, ServletException {
                request.setAttribute("exception", new Exception(CrowdConstant.MESSAGE_ACCESS_DENIED));
                request.getRequestDispatcher("/WEB-INF/system-error.jsp").forward(request, response);
            }
        })
        .and()
        .csrf()							// 防跨站请求伪造功能
        .disable()						// 禁用
        .formLogin()					// 开启表单登录的功能
        .loginPage("/admin/to/login/page.html")			// 指定登录页面
        .loginProcessingUrl("/security/do/login.html")	// 指定处理登录请求的地址
        .defaultSuccessUrl("/admin/to/main/page.html")	// 指定登录成功后前往的地址
        .usernameParameter("loginAcct")	// 账号的请求参数名称
        .passwordParameter("userPswd")	// 密码的请求参数名称
        .and()
        .logout()						// 开启退出登录功能
        .logoutUrl("/seucrity/do/logout.html")			// 指定退出登录地址
        .logoutSuccessUrl("/admin/to/login/page.html")	// 指定退出成功以后前往的地址
        ;

}
  • 常量的定义
public class CrowdConstant {
	
	public static final String MESSAGE_LOGIN_FAILED = "抱歉!账号密码错误!请重新输入!";
	public static final String MESSAGE_LOGIN_ACCT_ALREADY_IN_USE = "抱歉!这个账号已经被使用了!";
	public static final String MESSAGE_ACCESS_FORBIDEN = "请登录以后再访问!";
	public static final String MESSAGE_STRING_INVALIDATE = "字符串不合法!请不要传入空字符串!";
	public static final String MESSAGE_SYSTEM_ERROR_LOGIN_NOT_UNIQUE = "系统错误:登录账号不唯一!";
	public static final String MESSAGE_ACCESS_DENIED = "抱歉!您不能访问这个资源!";
	
	public static final String ATTR_NAME_EXCEPTION = "exception";
	public static final String ATTR_NAME_LOGIN_ADMIN = "loginAdmin";
	public static final String ATTR_NAME_PAGE_INFO = "pageInfo";

}
  • 实验效果:使用roleOperator无法访问管理员分页页面

access control 门禁管理系统初始账户密码_权限控制_33

4.5、权限认证:访问角色分页页面

4.5.1、启用权限验证
  • RoleHandler:@RequestMapping("/role/get/page/info.json")上启用权限验证,必须拥有【部长】角色才能访问该页面

access control 门禁管理系统初始账户密码_Mybatis_34

@PreAuthorize("hasRole('部长')")
@ResponseBody
@RequestMapping("/role/get/page/info.json")
public ResultEntity<PageInfo<Role>> getPageInfo(
    @RequestParam(value="pageNum", defaultValue="1") Integer pageNum,
    @RequestParam(value="pageSize", defaultValue="5") Integer pageSize,
    @RequestParam(value="keyword", defaultValue="") String keyword
) {		
    // 调用Service方法获取分页数据
    PageInfo<Role> pageInfo = roleService.getPageInfo(pageNum, pageSize, keyword);

    // 封装到ResultEntity对象中返回(如果上面的操作抛出异常,交给异常映射机制处理)
    return ResultEntity.successWithData(pageInfo);
}
  • WebAppSecurityConfig配置类中,启用全局方法权限控制功能

access control 门禁管理系统初始账户密码_权限控制_35

// 表示当前类是一个配置类
@Configuration

// 启用Web环境下权限控制功能
@EnableWebSecurity

//启用全局方法权限控制功能,并且设置prePostEnabled = true。保证@PreAuthority、@PostAuthority、@PreFilter、@PostFilter生效
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {
4.5.2、实验效果
  • adminOperator无法访问角色分页页面,但是很奇怪,并没有执行对应的异常映射页面

access control 门禁管理系统初始账户密码_Mybatis_36

access control 门禁管理系统初始账户密码_SpringSecurity_37

  • roleOperator可以访问角色分页页面

access control 门禁管理系统初始账户密码_Spring_38

4.6、基于注解的权限异常映射

4.6.1、基于注解的异常映射
  • 修改CrowdExceptionResolver异常映射类

access control 门禁管理系统初始账户密码_SpringSecurity_39

@ExceptionHandler(value = Exception.class)
public ModelAndView resolveException(
    Exception exception,
    HttpServletRequest request,
    HttpServletResponse response
) throws IOException {
    String viewName = "system-error";
    return commonResolve(viewName, exception, request, response);
}

// @ExceptionHandler将一个具体的异常类型和一个方法关联起来
private ModelAndView commonResolve(
    // 异常处理完成后要去的页面
    String viewName,
    // 实际捕获到的异常类型
    Exception exception,
    // 当前请求对象
    HttpServletRequest request,
    // 当前响应对象
    HttpServletResponse response
) throws IOException {
    // 1.判断当前请求类型
    boolean judgeResult = CrowdUtil.judgeRequestType(request);
    // 2.如果是Ajax请求
    if (judgeResult) {
        // 3.创建ResultEntity对象
        ResultEntity<Object> resultEntity = ResultEntity.failed(exception.getMessage());
        // 4.创建Gson对象
        Gson gson = new Gson();
        // 5.将ResultEntity对象转换为JSON字符串
        String json = gson.toJson(resultEntity);
        // 6.将JSON字符串作为响应体返回给浏览器
        response.getWriter().write(json);
        // 7.由于上面已经通过原生的response对象返回了响应,所以不提供ModelAndView对象
        return null;
    }
    // 8.如果不是Ajax请求则创建ModelAndView对象
    ModelAndView modelAndView = new ModelAndView();
    // 9.将Exception对象存入模型
    modelAndView.addObject("exception", exception);
    // 10.设置对应的视图名称
    modelAndView.setViewName(viewName);
    // 11.返回modelAndView对象
    return modelAndView;
}
  • 说明:基于注解的异常映射和基于 XML 的异常映射如果映射同一个异常类型,那么基于注解的方案优先。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rQGg1Rno-1600571611156)(day09【后台】权限控制-下.assets/image-20200619141343568.png)]

4.6.2、实验效果
  • adminOperator访问角色分页页面被拒绝

access control 门禁管理系统初始账户密码_Mybatis_40

4.6.3、关于异常映射的说明
  • 为什么这个异常映射能生效?还是这张图,程序执行顺序:DelegatingFilterProxy --> DispatcherServlet

access control 门禁管理系统初始账户密码_Spring_32

  • 基于注解配置,貌似配置的是拦截器(我猜的,因为有Pre前缀),所以程序能进入DispatcherServlet中,所以SpringMVC整套异常映射流程都能一步不落地走下来,也就能显示SpringMVC的异常映射页面
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {
@PreAuthorize("hasRole('部长')")
@ResponseBody
@RequestMapping("/role/get/page/info.json")
public ResultEntity<PageInfo<Role>> getPageInfo(
    @RequestParam(value="pageNum", defaultValue="1") Integer pageNum,
    @RequestParam(value="pageSize", defaultValue="5") Integer pageSize,
    @RequestParam(value="keyword", defaultValue="") String keyword
) {

4.7、权限验证:保存管理员信息

4.7.1、注解配置权限
  • AdminHandler中,添加@PreAuthorize("hasAuthority('user:save')")注解,拥有user:save权限的用户才能保存管理员信息

access control 门禁管理系统初始账户密码_Spring_42

@PreAuthorize("hasAuthority('user:save')")
@RequestMapping("/admin/save.html")
public String save(Admin admin) {		
    adminService.saveAdmin(admin);	
    return "redirect:/admin/get/page.html?pageNum="+Integer.MAX_VALUE;
}
  • 如何给一个资源分配不同的角色和权限:.access("hasRole('经理') OR hasAuthority('user:get')"):要求具备“经理”角色和“user:get”权限二者之一
@Override
protected void configure(HttpSecurity security) throws Exception {

    security
        .authorizeRequests()						// 对请求进行授权
        .antMatchers("/admin/to/login/page.html")	// 针对登录页进行设置
        .permitAll()								// 无条件访问

        // 放行静态资源
        .antMatchers("/bootstrap/**")	
        .permitAll()                    
        .antMatchers("/crowd/**")       
        .permitAll()                    
        .antMatchers("/css/**")         
        .permitAll()                    
        .antMatchers("/fonts/**")       
        .permitAll()                    
        .antMatchers("/img/**")         
        .permitAll()                    
        .antMatchers("/jquery/**")      
        .permitAll()                    
        .antMatchers("/layer/**")       
        .permitAll()                    
        .antMatchers("/script/**")      
        .permitAll()                    
        .antMatchers("/ztree/**")       
        .permitAll()
        .antMatchers("/admin/get/page.html")		// 针对管理员分页页面设定访问控制
        // .hasRole("经理")							// 要求具备经理角色
        .access("hasRole('经理') OR hasAuthority('user:get')")		// 要求具备“经理”角色和“user:get”权限二者之一
        .anyRequest()					// 其他任意请求
        .authenticated()				// 认证后访问
        .and()
        .exceptionHandling()
        .accessDeniedHandler(new AccessDeniedHandler() {		
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse response,
                               AccessDeniedException accessDeniedException) throws IOException, ServletException {
                request.setAttribute("exception", new Exception(CrowdConstant.MESSAGE_ACCESS_DENIED));
                request.getRequestDispatcher("/WEB-INF/system-error.jsp").forward(request, response);
            }
        })
        .and()
        .csrf()							// 防跨站请求伪造功能
        .disable()						// 禁用
        .formLogin()					// 开启表单登录的功能
        .loginPage("/admin/to/login/page.html")			// 指定登录页面
        .loginProcessingUrl("/security/do/login.html")	// 指定处理登录请求的地址
        .defaultSuccessUrl("/admin/to/main/page.html")	// 指定登录成功后前往的地址
        .usernameParameter("loginAcct")	// 账号的请求参数名称
        .passwordParameter("userPswd")	// 密码的请求参数名称
        .and()
        .logout()						// 开启退出登录功能
        .logoutUrl("/seucrity/do/logout.html")			// 指定退出登录地址
        .logoutSuccessUrl("/admin/to/login/page.html")	// 指定退出成功以后前往的地址
        ;

}
4.7.2、实验效果
  • adminOperator可以保存管理员信息

access control 门禁管理系统初始账户密码_Spring_43

5、目标 9:页面元素的权限控制

5.1、页面添加标签

  • admin-main.jsp页面上添加<security:authorize>标签
  • access="hasRole('经理')"表示拥有经理角色才能访问标签内的元素
  • access="hasAuthority('role:delete')表示拥有role:delete权限才能访问标签内的元素

access control 门禁管理系统初始账户密码_Mybatis_44

<security:authorize access="hasRole('经理')">
    <div class="col-xs-6 col-sm-3 placeholder">
        <img data-src="holder.js/200x200/auto/sky" class="img-responsive"
             alt="Generic placeholder thumbnail">
        <h4>Label</h4>
        <span class="text-muted">Something else</span>
    </div>
</security:authorize>
<security:authorize access="hasAuthority('role:delete')">
    <div class="col-xs-6 col-sm-3 placeholder">
        <img data-src="holder.js/200x200/auto/vine" class="img-responsive"
             alt="Generic placeholder thumbnail">
        <h4>Label</h4>
        <span class="text-muted">Something else</span>
    </div>
</security:authorize>

5.2、异常原因分析

  • 用户登录之后抛异常

access control 门禁管理系统初始账户密码_SpringMVC_45

  • AbstractAuthorizeTag.java:215处抛异常,我们不妨进去看一看

access control 门禁管理系统初始账户密码_SpringSecurity_46

access control 门禁管理系统初始账户密码_SpringSecurity_47

  • Debug启动:怪不得抛异常,有关SpringSecurityBean都在SpringMVC IOC容器中,在Spring里面找得到才怪。。。

access control 门禁管理系统初始账户密码_SpringMVC_48

@SuppressWarnings({ "unchecked", "rawtypes" })
private SecurityExpressionHandler<FilterInvocation> getExpressionHandler()
    throws IOException {
    ApplicationContext appContext = SecurityWebApplicationContextUtils.findRequiredWebApplicationContext(getServletContext());
    Map<String, SecurityExpressionHandler> handlers = appContext
        .getBeansOfType(SecurityExpressionHandler.class);

    for (SecurityExpressionHandler h : handlers.values()) {
        if (FilterInvocation.class.equals(GenericTypeResolver.resolveTypeArgument(
            h.getClass(), SecurityExpressionHandler.class))) {
            return h;
        }
    }

    throw new IOException(
        "No visible WebSecurityExpressionHandler instance could be found in the application "
        + "context. There must be at least one in order to support expressions in JSP 'authorize' tags.");
}

5.3、修改源码

  • 一不做二不休,老样子:同包名,同类名,复制粘贴,改源码,让程序去SpringMVC IOC容器中获取SpringSecurity需要的Bean

access control 门禁管理系统初始账户密码_SpringSecurity_49

@SuppressWarnings({ "unchecked", "rawtypes" })
private SecurityExpressionHandler<FilterInvocation> getExpressionHandler()
    throws IOException {
    // ApplicationContext appContext = SecurityWebApplicationContextUtils.findRequiredWebApplicationContext(getServletContext());

    // 1.获取ServletContext对象
    ServletContext servletContext = getServletContext();

    // 2.拼接SpringMVC在ServletContext域中的属性名
    String attrName = FrameworkServlet.SERVLET_CONTEXT_PREFIX + "springDispatcherServlet";

    // 3.从ServletContext域中获取IOC容器对象
    ApplicationContext appContext = (ApplicationContext) servletContext.getAttribute(attrName);

    Map<String, SecurityExpressionHandler> handlers = appContext
        .getBeansOfType(SecurityExpressionHandler.class);

    for (SecurityExpressionHandler h : handlers.values()) {
        if (FilterInvocation.class.equals(GenericTypeResolver.resolveTypeArgument(
            h.getClass(), SecurityExpressionHandler.class))) {
            return h;
        }
    }

    throw new IOException(
        "No visible WebSecurityExpressionHandler instance could be found in the application "
        + "context. There must be at least one in order to support expressions in JSP 'authorize' tags.");
}

5.4、实验结果

  • adminOperator访问是这三个球球

access control 门禁管理系统初始账户密码_Spring_50

  • roleOperator访问是这三个球球

access control 门禁管理系统初始账户密码_SpringSecurity_51