目录
spring security 框架简介
功能
- Authentication: 认证,就是用户登录
- Authorization: 授权,判断用户有什么权限
- 安全防护:防止跨站请求,session攻击
- 容易结合spring mvc
- 缓存支持、会话管理、加密功能、rememberMe功能
缺点
- 相对于shiro的配置和使用,比较复杂
- 必须依赖spring 容器,才可运行。shiro依赖性低,不需要依赖任何第三方框架,即可运行
认证过程
搭建
1、导入pom
spring-core
spring-web
spring-webmvc
spring-security-web
spring-security-config
jstl>jstl
javax.servlet>servlet-api
json工具:com.fasterxml.jackson.core>jackson-databind
可以不使用本地的tomcat,直接在当前pom.xml的目录下命令mvn tomcat:run运行项目
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<path>/ssl</path>
<port>8080</port>
<server>tomcat9</server>
</configuration>
</plugin>
</plugins>
</build>
2、配置web.xml
<!-- 启动spring -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:application.xml
<!-- 加载spring securityxml文件 -->
classpath:spring-security.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置springmve 前端控制器servlet -->
<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:springmvc.xml</param-value>
</init-param>
<!-- 在服务器启动时就启动 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- spring security过滤器链 -->
<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>
<!--设置filter编码过滤器,解决中文乱码问题-->
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
spring和springmvc xml模板
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"
default-autowire="byName"><!--自动装配(全局)-->
</beans>
注意!!!
spring-security.xml一定要有基本的配置,否则会报异常
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named ‘springSecurityFilterChain’ available
配置spring mvc.xml
<!-- 扫描controller包 -->
<context:component-scan base-package="com.lida" />
<!-- 注解方法配置处理器映射器和处理器适配器 -->
<!-- 配置这个就自动配置两个器 -->
<mvc:annotation-driven />
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/JSP/"></property>
<!-- 后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 处理静态资源,当有静态资源的请求时,就放行 -->
<mvc:default-servlet-handler/>
配置spring-security.xml
<!-- 过滤器链的配置 :1、需要拦截哪些资源 2、有什么资源和什么角色 3、定义登陆认证的方式,httpbasic,formlogin 4、定义登陆页面,定义登录请求地址,出现错误的处理方法
主要功能: 检查该次请求是否有权限 -->
<security:http>
<!-- 该项配置有顺序性 -->
<!-- permitAll()允许所有人都可以通过这个请求 isAnonymous()只限定匿名的人可以通过这个请求 -->
<security:intercept-url pattern="/index"
access="permitAll()" />
<security:intercept-url pattern="/userLoginPage"
access="permitAll()" />
<!--配置有ROLE_USER角色才可放行 hasAuthority("")判断有无该权限名字-->
<security:intercept-url pattern="/add"
access="hasRole('ROLE_USER')" />
<!-- 拦截所有请求资源,,isFullyAuthenticated()需要被认证才可通过该次请求 -->
<security:intercept-url pattern="/**"
access="isFullyAuthenticated()" />
<!-- 使用HttpBasic方式 -->
<!-- <security:http-basic /> -->
<!-- 使用表单登录的方式 -->
<!-- 自定义配置获取登陆页面的位置,若检测没有认证信息,会重定向到这个请求页面 -->
<!-- 自定义用户登陆的请求地址,即用户名和密码的存放上传地址 -->
<!-- 配置验证成功后的访问地址 -->
<!-- 配置自定义验证成功后的处理逻辑 和验证失败后的 -->
<!-- 返回页面是同步,,,逻辑处理后返回json,是异步-->
<security:form-login login-page="/userLoginPage"
login-processing-url="/userLogin" default-target-url="/successIndex"
authentication-success-handler-ref="myAuthenticationSuccessHandler"
authentication-failure-handler-ref="myAuthenticationFailureHandler" />
<!-- 自定义权限不足的处理配置 权限不足时自动请求这个地址 -->
<security:access-denied-handler
error-page="/errorPage" />
<!-- 关闭scrf -->
<security:csrf disabled="true" />
</security:http>
<!-- 认证配置 认证管理器 1、认证信息的提供方式,账户名、密码,当前用户权限 -->
<security:authentication-manager>
<security:authentication-provider>
<!-- 添加一个用户 后面是角色 硬编码方式 -->
<security:user-service>
<security:user name="user" password="password"
authorities="ROLE_USER" />
<security:user name="root" password="password"
authorities="ROLE_root" />
</security:user-service>
</security:authentication-provider>
<!-- 使用自定义的UserDetailsService -->
<security:authentication-provider
ref="MyUserDetailsService">
</security:authentication-provider>
</security:authentication-manager>
<bean name="myUserDetailsService" class="com.lida.MyUserDetailsService"></bean>
<bean name="myAuthenticationSuccessHandler" class="com.lida.MyAuthenticationSuccessHandler"></bean>
<bean name="myAuthenticationFailureHandler" class="com.lida.MyAuthenticationFailureHandler"></bean>
配置自定义认证提供者:UserDetailsService 和UserDetails
public class MyUserDetailsService implements UserDetailsService {
// 根据用户名获取用户信息 这里连接数据库
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 使用提供的默认的UserDetails实现类User 设置该用户所拥有的权限
User user = new User("user", "password",
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_ROOT"));
return user;
}
}
public class MyUserDetails implements UserDetails {
// 获取该账户的权限
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
public String getPassword() {
return null;
}
public String getUsername() {
return null;
}
// 该账户是不是不过期
public boolean isAccountNonExpired() {
return false;
}
// 该账户是不是被锁定
public boolean isAccountNonLocked() {
return false;
}
// 密码是不是不过期
public boolean isCredentialsNonExpired() {
return false;
}
// 该账户是不是可用
public boolean isEnabled() {
return false;
}
}
配置验证成功和失败后的处理逻辑
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
// 验证成功后的处理逻辑
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// 返回json字符串给前端
Map<String, Boolean> map = new HashMap<String, Boolean>();
map.put("success", true);
// 将对象转为json字符串
String string = new ObjectMapper().writeValueAsString(map);
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(string);
}
}
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
// 认证失败后的处理逻辑
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException)
throws IOException, ServletException {
// 返回json字符串给前端
Map<String, Boolean> map = new HashMap<String, Boolean>();
map.put("success", false);
// 将对象转为json字符串
String string = new ObjectMapper().writeValueAsString(map);
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(string);
}
}
整合ssm
RBAC数据库表
1、引入pom
org.mybatis>mybatis
org.mybatis>mybatis-spring 整合包
com.alibaba>druid 数据源
mysql>mysql-connector-java 驱动
org.springframework>spring-jdbc 使用spring的事务管理
2、整合mybatis
<context:component-scan base-package="com.lida.service"></context:component-scan>
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 连接池 -->
<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClass}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<!-- 最大线程数字 10个 -->
<property name="maxActive" value="10"></property>
<!-- 最长等待时间3秒 -->
<property name="maxWait" value="3000"></property>
</bean>
<!-- mybatis整合spring -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="datasource"></property>
<!-- 别名扫描 ,用于扫描写sql时的所要使用的实体 -->
<property name="typeAliasesPackage" value="com.lida.entity"></property>
</bean>
<!-- mapper接口扫描 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.lida.mapper"></property>
</bean>
<!-- 事务相关 -->
<bean
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启事务注解 -->
<tx:annotation-driven />
3、编写MyUserDetails ,Permission Role
注意!!MyUserDetails 是要求实现UserDetails接口的,所有数据库的字段名应该与它所要求的一致
public class MyUserDetails implements UserDetails {
private String username;
private String password;
private boolean enabled;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
}
public class Role {
private Integer id;
private String roleName;
private String roleDesc;
}
public class Permission {
private Integer id;
private String permName;
private String permTag; //这是权限的标识,
}
4、编写spring-security.xml
<security:http>
<!-- 拦截 -->
<security:intercept-url pattern="/prodect/index"
access="permitAll()" />
<security:intercept-url pattern="/userLoginPage"
access="permitAll()" />
<security:intercept-url pattern="/**"
access="isFullyAuthenticated()" />
<security:form-login login-page="/userLoginPage" />
</security:http>
<security:authentication-manager>
<security:authentication-provider
ref="myUserDetailsService">
<!-- 使用加密算法对用户输入进行加密,然后再与数据库里的匹配-->
<security:password-encoder ref="passwordEncoder"></security:password-encoder>
</security:authentication-provider>
</security:authentication-manager>
<!-- 引用加密算法 -->
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean>
<bean id="myUserDetailsService" class="com.lida.MyUserDetailsService"></bean>
5、编写MyUserDetailsService
public class MyUserDetailsService implements UserDetailsService {
@Autowired
UserMapper userMapper;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 查询用户信息
MyUserDetails myUserDetails = userMapper.findUserByUserName(username);
// 查询用户权限
List<Permission> permissions = userMapper.findPermissionByUserName(username);
// 存放所有的权限
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
// 构造满足条件的权限类
for (Permission permission : permissions) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission.getPermTag());
authorities.add(authority);
}
myUserDetails.setAuthorities(authorities);
return myUserDetails;
}
}
加入图形验证码
处理流程
1、定制过滤器:ImageCodeAuthenticationFilter
public class ImageCodeAuthenticationFilter extends OncePerRequestFilter{
//异常处理器
private AuthenticationFailureHandler authenticationFailureHandler;
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
//判断当前请求是否是登陆请求
if(request.getRequestURI().contains("/login")) {
try {
//校验验证码 正确的放在session里
String key = (String) request.getSession().getAttribute("key");
//获取用户输入的验证码
String imageCode =request.getParameter("imageCode");
if(StringUtils.isEmpty(imageCode.trim())) {
//抛出自定义的异常
throw new ImageCodeException("验证码必须输入");
}
if(!imageCode.trim().equals(key.trim())) {
throw new ImageCodeException("验证码错误");
}
} catch (AuthenticationException e) {
//将异常交给自定义的AuthenticationFailureHandler处理
authenticationFailureHandler.onAuthenticationFailure(request,response,e);
return ;
}
}
}
public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
this.authenticationFailureHandler = authenticationFailureHandler;
}
}
2、自定义异常类:ImageCodeException
public class ImageCodeException extends AuthenticationException {
public ImageCodeException(String msg,Throwable T) {
super(msg ,T);
}
public ImageCodeException(String msg) {
super(msg);
}
}
3、自定义失败处理类:MyAuthenticationFailureHandler
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
// 认证失败后的处理逻辑
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException)
throws IOException, ServletException {
// 返回json字符串给前端
Map map = new HashMap<String, Boolean>();
map.put("success", false);
//将错误信息传回浏览器
map.put("message", authenticationException.getMessage());
// 将对象转为json字符串
String string = new ObjectMapper().writeValueAsString(map);
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(string);
}
}
4、spring-security.xml整合:
<!-- 引用自定义过滤器,并注入自定义的异常处理类类 -->
<bean id="imageCodeAuthenticationFilter" class="com.lida.ImageCodeAuthenticationFilter">
<property name="authenticationFailureHandler" ref="myAuthenticationFailureHandler"></property>
</bean>
<bean id="myAuthenticationFailureHandler" class="com.lida.MyAuthenticationFailureHandler"></bean>
<security:http>
<!-- 使用自定义过滤器 并指定在form-login-filter过滤器之前-->
<security:custom-filter ref="myAuthenticationFailureHandler" before="FORM_LOGIN_FILTER"/>
</security:http>
remember me
处理流程
1、前端登陆页面的“记住我” 的表单的名字是:remember-me 这是固定的
2、spring-security.xml添加
<!-- 配置存储位置 -->
<bean id="jdbcTokenRepositoryImpl"
class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl">
<!-- 使用之前配置的druid数据源存储 -->
<property name="dataSource" ref="datasource" />
<!-- 程序启动的时候在数据库中自动建表 -->
<property name="createTableOnStartup" value="true" />
</bean>
<!-- 加上rememberme 功能 -->
<!-- 设置存储有效时间 -->
<security:remember-me token-repository-ref="jdbcTokenRepositoryImpl" token-validity-seconds="3600"/>
spring security 标签库
1、引入pom
org.springframework.security>spring-security-taglibs
2、jsp中引入
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="security"%>
<!-- 使用 -->
<security:authorize access="hasAuthority('ROLE_ADD_PRODECT')">
当用户有权限ROLE_ADD_PRODECT时候才渲染我呀
</security:authorize>
随时随地的获取用户信息
//获取登陆的用户名
UserDetails userDetails =(UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
整合spring boot
1、
@Configuration
@EnableWebSecurity //启动过滤连
public class SecurityConfig extends WebSecurityConfigurerAdapter{
//代替了<security:authentication-manager>
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").authorities("PRODUCT_ADD","PRODUCT_DELETE");
}
//代替了<security:http>
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/add").hasAnyAuthority("PRODUCT_ADD")
.antMatchers("/login").permitAll()
.antMatchers("/**")
.fullyAuthenticated()
.and()
.formLogin().loginPage("/login")
.and()
.csrf().disable();
}
}
2、
@Configuration
public class ErrorPageConfig {
@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
//定义错误页面:指定当匹配到那个错误时,做什么请求处理
public void customize(ConfigurableEmbeddedServlectContainer container) {
container.addErrorPages(new ErrorPage(HttpStatus.FORBIDDEN,"/403"));
}
}
}
}