SSM整合springsecurity过程
前言:先来说一下springsecurity的作用
- springsecurity底层实现为一条过滤器链,就是用户请求进来,判断有没有请求的权限,抛出异常,重定向跳转
- springsecurity的核心功能:
- 认证 (我是谁,用户信息验证)
- 授权 (可以做什么,权限角色)
- 攻击防护 (防止伪造攻击,csrf)
- 其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。
正文:我们来理一下整合过程
加入pom依赖
-
<!--spring的依赖,用来实现web功能需求--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </dependency> <!--security连接web的包,含有其他依赖包--> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> </dependency> <!--security配置所需的包--> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> </dependency> <!--security标签库的依赖包--> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> </dependency>
写web.xml配置
-
在web.xml中加入过滤器
<!--加入springsecurity的过滤器,实现security控制项目 --> <filter> <!--springSecurityFilterChain是默认名,不可更改,意思是security的过滤器链,在ioc容器初始化后,过滤器初始化--> <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>
创建Security基于注解的配置类,这里只是例子,两个方法开始为空
//表示当前类是配置类
@Configuration
//表示启用web下权限控制功能
@EnableWebSecurity
//启用全局方法权限控制功能,并且设置prePostEnabled=true,保证一些注解可以识别
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
//注入BCryptPasswordEncoder
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
//与springsecurity环境下用户登录相关
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
//内存版登录测试
// builder
// .inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).
// withUser("tom").password(new BCryptPasswordEncoder().encode("123")).roles("ADMIN");
//连接数据库的登录策略
builder.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);;
}
//与springsecurity环境下授权相关
@Override
protected void configure(HttpSecurity security) throws Exception {
security
.authorizeRequests()
.antMatchers("/admin/to/login/page.html") //释放登录表单页面
.permitAll()
.antMatchers("/security/admin/do/login.html") //释放登录的请求
.permitAll()
.antMatchers("/index.jsp", "/bootstrap/**", "/crowd_js/**",
"/css/**", "/fonts/**", "/img/**", "/jquery/**", "/layer/**", "/script/**", "/ztree/**")
.permitAll() //释放上面这些静态资源
.anyRequest() //禁止任何请求
.authenticated()
.and()
.exceptionHandling()
.accessDeniedHandler(new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletRequest.setAttribute("exception" ,new Exception(CrowdConstant.MESSAGE_ACCESS_DENIED));
httpServletRequest.getRequestDispatcher("/WEB-INF/system-error.jsp").forward(httpServletRequest, httpServletResponse);
}
})
.and()
.formLogin() //登录表单设置
.loginPage("/admin/to/login/page.html") //去向登录的页面
.loginProcessingUrl("/security/admin/do/login.html") //登录跳转的请求
.defaultSuccessUrl("/admin/to/main/page.html") //密码正确后跳转的页面
.usernameParameter("loginAcct") //表单的用户名
.passwordParameter("userPswd") //表单的密码
.and()
.csrf()
.disable() //禁用了csrf
.logout() //注销设置
.logoutUrl("/security/admin/do/logout.html") //注销跳的超链接
.logoutSuccessUrl("/admin/to/login/page.html"); //注销成功后跳转的页面
}
}
- 我放在了mvc包下,扫描它的是springmvc.xml这个配置文件,留个悬念!!!
来是一波分析
由图可以看到,ssm框架整合后有两个xml配置文件,一个是spring的ioc容器叫spring-*.xml ,一个是springmvc.xml配置文件。在web.xml文件里面是分别配置,分管不同的功能的。
- application.xml是父类ioc容器,放的是service层和mapper层,以及pojo实体类的bean,里面会扫描service层和mapper,以至于放进ioc容器,可以通过@AutoWrited注入使用
然而psringmvc.xml扫描的是mvc包,包下包含了config包
为什么要分析这些呢?
- 因为会出现没有bean的错误 是springSecurityFilterChain这个bean,很眼熟吧
错误分析
- 为什么会报这样的错:因为ssm中有两大容器
- 一个是spring的ioc容器:
ContextLoaderListener
- 一个是springmvc的ioc容器:
DispatcherServlet
- 一个是spring的ioc容器:
在项目启动时:首先ContextLoaderListener初始化,其次springSecurityFilterChain初始化,最后DispatcherServlet初始化;
springSecurityFilterChain初始化时会默认去寻找springmvc的ioc容器扫描配置类
所以顺序就是:ContextLoaderListener->springSecurityFilterChain->DispatcherServlet
也可以看出来,Listener监听器—>Filter过滤器—>servlet初始化,这一规律。
错误解决方案
-
解决方案有两种:
-
- 将springmvc的ioc容器和spring的ioc容器合二为一。
在web.xml文件中注释掉初始化
ContextLoaderListener
的配置<!--配置spring 初始化器--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-*.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
然后在springmvc初始化文件中加入
<import resource="classpath:spring-*.xml"/>
初始化spring的ioc容器的配置
-
- 第二种是修改源码的方法,小编就是用了这种方法的
首先来分析我们的错误,核心原因是过滤器在加载前端控制器之前就初始化了,初始化时会去前端控制器ioc容器中找bean,那时候前端控制器根本没有
我们看看错误出在哪里,那个类抛的异常找到的是这个类org.springframework.web.filter.DelegatingFilterProxy
我们用idea的ctrl+鼠标单击进去,在自己的项目下建立一样的包,建立一样的内容同名的类,进行对源码的修改
这是初始化springSecurityFilterChain的过程,findWebApplicationContext()这个方法就是在找容器,这时候找前端控制器是找不到的,但是找不到这里是没有处理找不到的情况的,但最后是会报封装不上springSecurityFilterChain的异常
然而我们还有机会扫描它,我们先把这一步取消掉,那就是取消了初始化springSecurityFilterChain,如上,当发起第一次请求的时候,它会执行一个叫**doFilter()**的方法,这也和一般的过滤器一般,当服务器接收到请求时,会先去到过滤器执行doFilter()方法,然后是否放行什么的操作。
又看到我们的好朋友WebApplicationContext wac = findWebApplicationContext();了吧,对,它这里又开始找ioc容器去找相应的bean,但是这个方法好像只在找spring的ioc容器的,我们需要自定义方法,从源码中我们重写了一个找ioc容器的过程。
修改的代码如下,获取ServletContext,然后attrName是前端控制器的全类名一样的东西,重要的是servletName,这是在web.xml对应的前端控制器的名字
> 如上这些做完就完成了对源码的修改,项目启动成功了,整合也就完成了,在详细的步骤就是security配置类的编写了。
用内存版模式去搭建登录
-
先做准备工作
-
设置登录页面的信息
-
在configure带builder的方式,使用内存版登录测试
- 这样就可以尝试登录了。
改用数据库版的登录模式
- 建立数据库
2. 编写UserDetailService子类
package com.frf.crowd.mvc.config;
import com.frf.crowd.pojo.Admin;
import com.frf.crowd.pojo.Role;
import com.frf.crowd.service.AdminService;
import com.frf.crowd.service.AuthService;
import com.frf.crowd.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.rmi.server.UID;
import java.util.ArrayList;
import java.util.List;
/**
* 用于springSecurity用户验证时获取用户信息
* @author Administrator
*
*/
@Component
public class CrowdUserDetialService implements UserDetailsService {
@Autowired
private AdminService adminService;
@Autowired
private AuthService authService;
@Autowired
private RoleService roleService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//通过用户名查找用户
Admin admin = adminService.get_Admin_By_Name(username);
//获取此管理员id
Integer adminId = admin.getId();
//根据adminId查找角色信息
List<Role> roleList = roleService.get_Assign_Role_By_AmindID(adminId);
//根据adminID查找权限
List<String> authNameList = authService.get_Assigned_AuthName_By_AdminId(adminId);
//创建集合对象存储 权限集合——GrantedAuthority
List<GrantedAuthority> authorities=new ArrayList<>();
//遍历assignedRoleList存入角色信息
for (Role role : roleList) {
String roleName="ROLE_"+role.getName();
SimpleGrantedAuthority grantedRole = new SimpleGrantedAuthority(roleName);
authorities.add(grantedRole);
}
//遍历authNameList存入权限信息
for (String authName : authNameList) {
SimpleGrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authName);
authorities.add(grantedAuthority);
}
//封装到我们设置的对象中返回
SecurityAdmin securityAdmin = new SecurityAdmin(admin, authorities);
return securityAdmin;
}
}
-
编写封装类SecurityAdmin,User的子类
package com.frf.crowd.mvc.config; import com.frf.crowd.pojo.Admin; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import java.util.Collection; import java.util.List; public class SecurityAdmin extends User { //原始的Admin对象,包含Admin对象的全部属性 private Admin originalAdmin; //重写父类的方法 public SecurityAdmin( //传入封装前的Admin对象 Admin originalAdmin, //传入创建角色、权限信息的集合 List< GrantedAuthority> authorities) { //调用User封装 super(originalAdmin.getUserName(), originalAdmin.getUserPswd(), authorities); //成员变量获取原始Admin this.originalAdmin=originalAdmin; //擦除密码 this.originalAdmin.setUserPswd(null); } //对外提供获取原始Admin对象的get方法 public Admin getOriginalAdmin() { return originalAdmin; } }
-
装配数据源
-
写配置文件,在初始化的时候放入ioc容器中
SecurityAdmin是一个经过封装的User类,继承了User类,对此,我们就可以把我们用于数据库登录的实体类作为原始类加上角色和权限封装成一个新类。 -
进行自动装配 到此,我们就配置基本成功了。
到此,我们就配置基本成功了。
-