day09【后台】权限控制-下
1、目标5:密码加密
1.1、修改数据库表结构
- 由于之前使用
MD5
加密,密码字段长度为32
位;现采用带盐值的加密,需要修改密码字段的长度
- 顺带把密码也改了
1.2、注入BCryptPasswordEncoder对象
- 注 意 : 如果在
SpringSecurity
的配置类中用@Bean
注解将BCryptPasswordEncoder
对象存入IOC
容器, 那么Service
组件将获取不到BCryptPasswordEncoder
对象。儿子可以用他老爹的东西,但他老爹并不能用他儿子的东西。。。
- 我们需要在
Spring
的配置文件中注入BCryptPasswordEncoder
对象,这样SpringMVC IOC
容器也能使用该对象
<!-- 配置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
对象,并使用其对密码进行加密
@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
能够正常登陆
- 创建管理员
NiuNiu
,密码带盐值哟
2、目标 6: 显示用户昵称
2.1、修改顶部navbar
2.1.1、导入SpringSecurity标签库
- 在
include-nav.jsp
顶部导航栏中,引入SpringSecurity
标签库
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="security" %>
2.1.2、获取登录用户的信息
- 修改获取用户昵称的代码
<security:authentication property="principal.originalAdmin.userName"/>
2.2、分析的过程
- 分析过程如下
<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
类吗?
-
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、实验效果
- 乌拉~
3、目标 7:密码的擦除
3.1、擦除密码与凭证
- 在如下打上断点,用户登录时则会触发此断点,将会执行擦除用户凭证的操作
@Override
public void eraseCredentials() {
super.eraseCredentials();
credentials = null;
}
-
Step into
进入super.eraseCredentials()
方法
public void eraseCredentials() {
eraseSecret(getCredentials());
eraseSecret(getPrincipal());
eraseSecret(details);
}
Step into
进入eraseSecret(getCredentials())
方法:if (secret instanceof CredentialsContainer) {
条件并不满足
private void eraseSecret(Object secret) {
if (secret instanceof CredentialsContainer) {
((CredentialsContainer) secret).eraseCredentials();
}
}
Step into
进入eraseSecret(getPrincipal())
方法:if (secret instanceof CredentialsContainer) {
条件成立,并且secret
对象的类型就是我们自定义的SecurityAdmin
类型
private void eraseSecret(Object secret) {
if (secret instanceof CredentialsContainer) {
((CredentialsContainer) secret).eraseCredentials();
}
}
Step into
进入((CredentialsContainer) secret).eraseCredentials()
方法:password = null
:擦除User
类中的password
,并没有擦除我们自定义类中的密码
public void eraseCredentials() {
password = null;
}
eraseSecret(details)
方法:擦除IP
地址、SessionId
等信息
因为if (secret instanceof CredentialsContainer) {
条件不满足,所以也不会执行
- 擦除
credentials
,将其赋值为null
- 结论:
- 擦除
User
类中的password
属性 - 擦除
credentials
对象
3.2、擦除SecurityAdmin的密码
- 擦除自定义类中的密码字段
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
字段被擦除
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
- 添加两个管理员(用户):
adminOperator
、roleOperator
4.2.3、t_auth
- 添加
user:save
权限
4.2.4、t_role
- 添加如下四个角色
4.3、分配角色与权限
4.3.1、给管理员分配角色
4.3.2、给角色分配权限
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
可以访问管理员分页页面
-
roleOperator
无法访问管理员分页页面
4.4.3、关于异常映射的说明
- 为什么显示
403
页面?程序执行顺序:DelegatingFilterProxy --> DispatcherServlet
- 如下配置直接将权限验证交由
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
无法访问管理员分页页面
4.5、权限认证:访问角色分页页面
4.5.1、启用权限验证
- 在
RoleHandler:@RequestMapping("/role/get/page/info.json")
上启用权限验证,必须拥有【部长】角色才能访问该页面
@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
配置类中,启用全局方法权限控制功能
// 表示当前类是一个配置类
@Configuration
// 启用Web环境下权限控制功能
@EnableWebSecurity
//启用全局方法权限控制功能,并且设置prePostEnabled = true。保证@PreAuthority、@PostAuthority、@PreFilter、@PostFilter生效
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {
4.5.2、实验效果
-
adminOperator
无法访问角色分页页面,但是很奇怪,并没有执行对应的异常映射页面
-
roleOperator
可以访问角色分页页面
4.6、基于注解的权限异常映射
4.6.1、基于注解的异常映射
- 修改
CrowdExceptionResolver
异常映射类
@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
访问角色分页页面被拒绝
4.6.3、关于异常映射的说明
- 为什么这个异常映射能生效?还是这张图,程序执行顺序:
DelegatingFilterProxy --> DispatcherServlet
- 基于注解配置,貌似配置的是拦截器(我猜的,因为有
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
权限的用户才能保存管理员信息
@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
可以保存管理员信息
5、目标 9:页面元素的权限控制
5.1、页面添加标签
- 在
admin-main.jsp
页面上添加<security:authorize>
标签
-
access="hasRole('经理')"
表示拥有经理角色才能访问标签内的元素 -
access="hasAuthority('role:delete')
表示拥有role:delete
权限才能访问标签内的元素
<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、异常原因分析
- 用户登录之后抛异常
- 在
AbstractAuthorizeTag.java:215
处抛异常,我们不妨进去看一看
-
Debug
启动:怪不得抛异常,有关SpringSecurity
的Bean
都在SpringMVC IOC
容器中,在Spring
里面找得到才怪。。。
@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
@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
访问是这三个球球
-
roleOperator
访问是这三个球球