Security
Security常用配置
认证和授权的概念
- 认证:登录(用户名和密码)
- 登录成功即可认证成功
- 授权:访问系统功能的权限
权限模块的数据模型
- 用户表
- 角色表
- 必须是ROLE_开头
- 权限表
security入门
依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
内部封装了Filter,
web.xml容器中配置一个过滤器–代理过滤器,真实的过滤器在spring的容器中配置
配置xml
整合Spring Security时过滤器的名称必须为springSecurityFilterChain
<filter>
<!--
过滤器的名称必须为springSecurityFilterChain,
否则会抛出NoSuchBeanDefinitionException异常
-->
<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>
<!-- 2:springmvc的核心控制器-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定加载的配置文件 ,通过参数contextConfigLocation加载 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-security.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
配置文件
spring-security.xml
<!--
form-login:定义表单登录信息
login-page="/login.html":表示指定登录页面
username-parameter="username":使用登录名的名称,默认值是username
password-parameter="password":使用登录名的密码,默认值是password
login-processing-url="/login.do":表示登录的url地址
default-target-url="/index.html":登录成功后的url地址
authentication-failure-url="/login.html":认证失败后跳转的url地址,失败后指定/login.html
always-use-default-target="true":登录成功后,始终跳转到default-target-url指定的地址,即登录成功的默认地址
-->
<security:http auto-config="true" use-expressions="true">
<security:intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"></security:intercept-url>
<security:form-login login-page="/login.html"
username-parameter="username"
password-parameter="password"
login-processing-url="/login.do"
default-target-url="/index.html"
authentication-failure-url="/login.html"
always-use-default-target="true"
/>
<!--
csrf:对应CsrfFilter过滤器
disabled:是否启用CsrfFilter过滤器,如果使用自定义登录页面需要关闭此项,否则登录操作会被禁用(403)
-->
<security:csrf disabled="true"></security:csrf>
</security:http>
对密码进行加密
框架自带加密方式
BCryptPasswordEncoder
在spring-security.xml文件中指定密码加密对象
<!--
三:认证管理,定义登录账号名和密码,并授予访问的角色、权限
authentication-manager:认证管理器,用于处理认证操作
-->
<security:authentication-manager>
<!--
authentication-provider:认证提供者,执行具体的认证逻辑
-->
<security:authentication-provider user-service-ref="userService">
<!--指定密码加密策略-->
<security:password-encoder ref="passwordEncoder"></security:password-encoder>
</security:authentication-provider>
</security:authentication-manager>
<!--配置密码加密对象-->
<bean id="passwordEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
注解方式权限控制
spring-security.xml文件中配置组件扫描和mvc的注解驱动,用于扫描Controller
开启权限注解支持
Controller的方法上加入注解(@PreAuthorize)进行权限控制
//包扫描
<context:component-scan base-package="com"/>
//注解驱动
<mvc:annotation-driven></mvc:annotation-driven>
<!--开启注解方式权限控制-->
<security:global-method-security pre-post-annotations="enabled" />
创建Controller类并在Controller的方法上加入注解
@RestController
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/add")
@PreAuthorize("hasAuthority('add')")//表示用户必须拥有add权限才能调用当前方法
public String add(){
System.out.println("add...");
return "success";
}
@RequestMapping("/update")
@PreAuthorize("hasRole('ROLE_ADMIN')")//表示用户必须拥有ROLE_ADMIN角色才能调用当前方法
public String update(){
System.out.println("update...");
return "success";
}
@RequestMapping("/delete")
@PreAuthorize("hasRole('ABC')")//表示用户必须拥有ABC角色才能调用当前方法
public String delete(){
System.out.println("delete...");
return "success";
}
}
退出登录
a连接
<a href="/logout.do">退出登录</a>
spring-security.xml定义:
<!--
logout:退出登录
logout-url:退出登录操作对应的请求路径
logout-success-url:退出登录后的跳转页面
invalidate-session="true" 默认为true,用户在退出后Http session失效
-->
<security:logout logout-url="/logout.do"
logout-success-url="/login.html" invalidate-session="true"/>
总结
不需要登录,权限 角色 就可以访问
<security:http security="none" pattern="/js/**"></security:http>
<security:http security="none" pattern="/css/**"></security:http>
<security:http security="none" pattern="/login.html"></security:http>
使用指定的登录页面
<security:form-login login-page="/login.html"
username-parameter="username"
password-parameter="password"
login-processing-url="/login.do"
default-target-url="/index.html"
authentication-failure-url="/login.html"
always-use-default-target="true"/>
从数据库查询用户信息,对密码进行加密
<security:authentication-manager>
<security:authentication-provider user-service-ref="userService">
<security:password-encoder ref="bCryptPasswordEncoder"></security:password-encoder>
</security:authentication-provider>
</security:authentication-manager>
<bean id="bCryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean>
对访问的页面做权限控制
<security:intercept-url pattern="/index.html" access="isAuthenticated()"></security:intercept-url>
<security:intercept-url pattern="/a.html" access="isAuthenticated()"></security:intercept-url>
<security:intercept-url pattern="/b.html" access="hasAuthority('add')"></security:intercept-url>
<security:intercept-url pattern="/c.html" access="hasRole('ROLE_ADMIN')"></security:intercept-url>
<security:intercept-url pattern="/d.html" access="hasRole('ABC')"></security:intercept-url>
注解方式权限控制
<security:global-method-security pre-post-annotations="enabled"></security:global-method-security>
退出登录
<security:logout logout-url="/logout.do" logout-success-url="/login.html" invalidate-session="true"></security:logout>
SpringSecurity-原理
代码必须配置
名字必须叫springSecurityFilterChain
<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>
当我们使用基于Spring Security的NameSpace进行配置时
系统会自动为我们注册一个名为springSecurityFilterChain类型为FilterChainProxy的bean,所以我们声明的过滤器必须叫springSecurityFilterChain
DelegatingFilterProxy
DelegatingFilterProxy
代理的就是名为 SpringSecurityFilterChain的Filter
本身是和spring security无关,继承于抽象类GenericFilterBean,间接地实现了javax.servlet.Filter接口
FilterChainProxy
获取过滤器链中的过滤器,封装为虚拟的VirtualFilterChain对象,并开始执行过滤
public class DelegatingFilterProxy extends GenericFilterBean {
protected void initFilterBean() throws ServletException {
// 找到 springSecurityFilterChain
this.targetBeanName = getFilterName();
}
}
vfc全称 VirtualFilterChain对象
UsernamePasswordAuthenticationFilter
处理 form 登陆的过滤器
① 属性中增加了username和password字段;
② 强制的只对POST请求应用;
③ 重写了attemptAuthentication身份验证入口方法。
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter{
// 判断参数是否是username和password
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
// 必须是post提交
private boolean postOnly = true;
public Authentication attemptAuthentication{
// 获取用户名和密码
// request 中提取用户名和密码。
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
//用户名去两边空格
username = username.trim();
// 封装用户名和密码
// 封装类UsernamePasswordAuthenticationToken ,用来存储及传递用户名和用户密码。
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// 获取权限管理器
//调用getAuthenticationManager()方法获取AuthenticationManager实例
//authenticate()方法对封装在authRequest [UsernamePasswordAuthenticationToken]中的用户名及密码进行验证。
return this.getAuthenticationManager().authenticate(authRequest);
}
}
AuthenticationManager
这里可以理解为 spring security 配置文件中 <authentication-manager/>
的实现类
执行顺序
1) 用户通过url:/login 登录,该过滤器接收表单用户名密码
2) 判断用户名密码是否为空
3) 生成 UsernamePasswordAuthenticationToken
4) 将 Authentiction 传给 AuthenticationManager接口的 authenticate 方法进行认证处理
5) AuthenticationManager 默认是实现类为 ProviderManager ,ProviderManager 委托给 AuthenticationProvider 进行处理
6) UsernamePasswordAuthenticationFilter 继承了 AbstractAuthenticationProcessingFilter 抽象类,AbstractAuthenticationProcessingFilter 在 successfulAuthentication 方法中对登录成功进行了处理,通过 SecurityContextHolder.getContext().setAuthentication() 方法将 Authentication 认证信息对象绑定到 SecurityContext
7) 下次请求时,在过滤器链头的 SecurityContextPersistenceFilter 会从 Session 中取出用户信息并生成 Authentication(默认为 UsernamePasswordAuthenticationToken),并通过 SecurityContextHolder.getContext().setAuthentication() 方法将 Authentication 认证信息对象绑定到 SecurityContext
8) 需要权限才能访问的请求会从 SecurityContext 中获取用户的权限进行验证
DaoAuthenticationProvider
解析并认证,需要一个提供者,这就是提供者
就是根据 UsernamePasswordAuthenticationToken,获取到 username,然后调用 UserDetailsService 检索用户详细信息
public class DaoAuthenticationProvider{
protected final UserDetails retrieveUser{
UserDetails loadedUser = getUserDetailsService().loadUserByUsername(username);
}
}
进入到自定义用户名的方法
@Component
public class UserService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) {
// 交给springsecurity进行认证和授权
return new User(username,password,list);
}
}
AuthorityUtils
是一个工具包,用于操作 GrantedAuthority 集合的实用方法:
commaSeparatedStringToAuthorityList(String authorityString):从逗号分隔符中创建 GrantedAuthority 对象数组,帮我们快速创建出权限的集合
注解配置
web.xml
配置
添加 SpringSecurity
的 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>
<!--设置核心控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
spring配置 : spring.xml
,使用的是默认的视图解析器
<!--包扫描-->
<context:component-scan base-package="com.kevin" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".html"></property>
</bean>
<mvc:annotation-driven />
<mvc:default-servlet-handler />
新建 IndexController
@Controller
public class IndexController {
@GetMapping("index")
@ResponseBody
public String index(){
return "success";
}
}
设置配置类
@Configuration
@EnableWebSecurity
public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder password(){
return new BCryptPasswordEncoder();
}
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()// 自定义自己编写的登录页面
.loginPage("/login.html") // 配置哪个 url 为登录页面
.loginProcessingUrl("/login") // 设置哪个是登录的 url。
.defaultSuccessUrl("/views/success.html") // 登录成功之后跳转到哪个 url
.failureForwardUrl("/views/failure.html") // 登录失败之后跳转到哪个 url
.permitAll()
.and().authorizeRequests()
.antMatchers("/views/index.html").permitAll() //设置哪些路径可以直接访问,不需要认证
.anyRequest() // 其他请求
.authenticated() //需要认证
// 关闭 csrf
//退出连接 .logout().logoutUrl("/logout").logoutSuccessUrl("/login.html").permitAll()
.and().csrf().disable();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//加密策略
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
}
基于角色或权限进行访问控制
hasAuthority 方法,如果当前的主体具有指定的权限,则返回 true,否则返回 false
hasAnyAuthority 方法,有任何提供的角色,返回true
hasRole 方法
如果用户具备给定角色就允许访问,否则出现 403。
如果当前主体具有指定的角色,则返回 true。
配置文件中不需要添加”ROLE_“,因为底层代码会自动添加与之进行匹配
hasAnyRole方法, 表示用户具备任何一个条件都可以访问。
自定义编写实现类
@Service("userDetailsService")
public class LoginService implements UserDetailsService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String queryUser = "SELECT * FROM `users` WHERE username=?";
//1、查询指定用户的信息
Map<String, Object> map = jdbcTemplate.queryForMap(queryUser, username);
if(map == null) {
throw new UsernameNotFoundException("用户名不存在!");
}
//2、将查询到的用户封装到框架使用的UserDetails里面
return new User(map.get("username").toString(), map.get("password").toString(),
AuthorityUtils.createAuthorityList("ADMIN","USER"));//暂时写死,过后数据库中查
}
}
开启注解功能!
@Configuration
@EnableWebSecurity
//开启注解功能!
@EnableGlobalMethodSecurity(securedEnabled=true,prePostEnabled = true)
public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter {
}
// 测试注解:
@Secured这里匹配的字符串需要添加前缀“ROLE_“。
@Controller
public class IndexController {
// 测试注解:
@RequestMapping("testSecured")
@ResponseBody
// @Secured({"ROLE_normal","ROLE_admin"})
//@PreAuthorize("hasRole('ROLE_管理员')")
@PreAuthorize("hasAnyAuthority('admins')")
public String helloUser() {
return "hello,user";
}
}
注意事项
页面提交方式必须为 post 请求,用户名,密码必须为
username,password
页面添加记住我复选框
<form action="/login" method="post">
username:<input type="text" name="username"><br>
password:<input type="password" name="password"><br>
记住我:<input type="checkbox"name="remember-me"title="记住密码"/><br/>
<input type="submit" value="submit">
</form>
name 属性值必须位 remember-me.不能改为其他值
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new
JdbcTokenRepositoryImpl();
// 赋值数据源
jdbcTokenRepository.setDataSource(dataSource);
// 自动创建表,第一次执行会创建,以后要执行就要删除掉!
// jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
实现记住我
.and()
//实现记住我
.rememberMe()
//多久时间免登录,
.tokenValiditySeconds(7200)// 单位秒
//往数据库缓存
.tokenRepository(persistentTokenRepository())
//自动登录
.userDetailsService(userDetailsService)