首先我们要知道spring security适应来干嘛,主要功能是什么,
Spring Security 参考手册
这是springsecurity的官方手册,如果需要详细了解可前往此处;
- spring的核心功能主要有三个:
– 认证(认证你的角色是什么)
– 授权(你能干什么)
– 攻击防护(防止伪造是否) - 核心就是一组通过职责链的过滤器链;
第一步当然是先要将相关的依赖包加入,这里我加入到我ssm项目中的父项目中
<dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.2.10.RELEASE</version>
</dependency>
<!-- 标签库 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>4.2.10.RELEASE</version>
</dependency>
</dependencies>
第二步就是编写SpringSecurity的配置 在web.xml配置Security的Filter拦截所有请求
<!-- 权限过滤Filter -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
第三步就是编写SpringSecurity的配置类;给个模版吧
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO Auto-generated method stub
//super.configure(auth);
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/static/**","/login.jsp").permitAll()
.anyRequest().authenticated();//剩下都需要认证
// /login.jsp==POST 用户登陆请求发给Security
http.formLogin().loginPage("/toLogin")
.usernameParameter("loginacct").passwordParameter("userpswd")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/main").permitAll();
http.csrf().disable();
http.logout().logoutSuccessUrl("/index");
//异常处理器
http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
String type=request.getHeader("X-Requested-With");
if ("XMLHttpRequest".equals(type)) {
//ajax 通过流的形式
//response.getWriter().print("403");//403权限不够
}else {
request.getRequestDispatcher("/WEB-INF/jsp/error/error403.jsp").forward(request, response);
}
}
});
http.rememberMe();
}
}
- 可以看到这个配置类是需要继承springsecurity所提供的WebSecurityConfigurerAdapter的适配器,且还得通过注解开启相关功能
- @EnableWebSecurity // 声明式配置,启用SpringSecurity安全机制
- @EnableGlobalMethodSecurity(prePostEnabled = true) //开启细粒度全局方法级别权限控制功能 需要实现细粒话控制就得开启这个
- 注意的事项:
– 如果想开启csrf防跨站攻击,必须post方式的/logout请求,表单中需要增加csrf token;如果不禁用csrf,默认是开启的状态;页面不设置csrf表单域,那么,提交登录请求会报错 刷新页面其中的值是不会变化,一旦你登入成功才会改变;如果你登入成功,这个值会被删除,因为他是保持在session中,查询回到登陆页和后退页面,这个值就会重新生成
UserDetailsService类 通过自定义DAO认证方式 最终返回的是一个SpringSecurity所提供的User,在我博客中对SpringSecurity表单认证的过滤器有解释这个类;
@Component
public class SecurityUserDetailServiceImpl implements UserDetailsService {
@Autowired
TAdminMapper adminMapper;
@Autowired
TRoleMapper roleMapper;
@Autowired
TPermissionMapper permissionMapper;
Logger log=LoggerFactory.getLogger(SecurityUserDetailServiceImpl.class);
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// TODO Auto-generated method stub
/*
* SELECT t_role.* FROM t_role JOIN t_admin_role ON t_role.id = t_admin_role.roleid WHERE t_admin_role.adminid=2
*
*
* */
// 1查询用户对象
TAdminExample example = new TAdminExample();
example.createCriteria().andLoginacctEqualTo(username);
List<TAdmin> list = adminMapper.selectByExample(example);
log.debug("用户信息{}",list);
if (list != null && list.size() == 1) {
TAdmin admin = list.get(0);
Integer adminId = admin.getId();
log.debug("用户信息{}",admin);
// 1查询角色集合
List<TRole> roleList = roleMapper.listRoleByAdminId(adminId);
log.debug("用户角色{}",roleList);
// 查询权限集合
List<TPermission> permissionList = permissionMapper.listPermissionByAdminId(adminId);
// 构建用户所有权限集合==》(ROLE_角色+权限)
Set<GrantedAuthority> authorities=new HashSet<GrantedAuthority>();
for (TRole role : roleList) {
authorities.add(new SimpleGrantedAuthority("ROLE_"+role.getName()));
}
for (TPermission permission : permissionList) {
authorities.add(new SimpleGrantedAuthority("ROLE_"+permission.getName()));
}
log.debug("用户总权限集合{}",authorities);
// 第一个参数分别是 账号 密码 权限集合
//return new User(admin.getLoginacct(), admin.getUserpswd(), authorities);
return new TSecurityAdmin(admin, authorities);
}else {
return null;
}
}
}
- 为什么我会把系统原先的User类给注释,因为可以通过继承来封装一个自定义的User类;原因是如果还需要往里面增加其他属性就显得力不从心,因为User的构造方法最多只提供了7个属性的赋值,上篇博客有讲到这些属性在源码中的应用;因为只能存储到账号密码和密码,但是在实际的使用上,我们还需要使用查询的用户的其他的属性;
public class TSecurityAdmin extends User {
TAdmin admin;
public TSecurityAdmin(TAdmin admin,Set<GrantedAuthority> authorities) {
super(admin.getLoginacct(), admin.getUserpswd(), true, true, true, true, authorities);
this.admin=admin;
}
}
然后就是让SpringMVC来扫描所有的组件,因为细粒化控制注解需加在controller中,原因是Security的配置是在根容器(Spring配置文件)Spring配置文件没有扫SpringMVC的东西,导致SpringMVC中的组件不能被,所以在controller中的权限控制注解不成功
- 解决方法1:在controller方法里的service中的具体方法实现累添加权限注解,因为除了controller的组件都是spring管理;
- 方法2:删除spring ioc容器,让springMVC扫描全部组件;这里我们选择这种
所以在web.xml中注释掉了spring容器;让后将spring容器的配置文件加入到springMVC中:
<!-- 核心控制器 -->
<!-- The front controller of this Spring Web application, responsible for
handling all application requests -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:/spring/springmvc.xml
classpath*:/spring/spring-*.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map all requests to the DispatcherServlet for handling -->
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 创建Spring IOC容器 -->
<!-- needed for ContextLoaderListener -->
<!-- <context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:/spring/spring-*.xml</param-value>
</context-param>
Bootstraps the root web application context before servlet initialization
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> -->
然后就是给我们的方法标注权限注解了比如在Controller中,控制doAdd这个方法的访问只能是项目经理
@PreAuthorize("hasRole('项目经理')")
@RequestMapping("admin/doAdd")
public String doAdd(TAdmin admin) {
adminService.saveTAdmin(admin);
return "redirect:/admin/index?pageNum="+Integer.MAX_VALUE;
页面中元素也是可以被控制的,通过springsecurity的jsp标签来实现;如下面代码:认证的用户的角色钥匙组长才能看到这个帮助的图标
<sec:authorize access="hasRole('组长')">
<button type="button" class="btn btn-default btn-danger">
<span class="glyphicon glyphicon-question-sign"></span> 帮助
</button>
</sec:authorize>