SpringSecurity
作用:做登陆【认证】和授权的
代码位置:E:\010-SpringSecurity\002-springsecurity-thymeleaf
1. 认证授权的基础概念
1.1 什么是认证(登录)
进入移动互联网时代,大家每天都在刷手机,常用的软件有微信、支付宝、头条等,下边拿微信来举例子说明认证相关的基本概念,在初次使用微信前需要注册成为微信用户,然后输入账号和密码即可登录微信,输入账号和密码登录微信的过程就是****认证****。
系统为什么要认证?
http://127.0.0.1:8080/getAllUser
http://127.0.0.1:8080/addUser
http://127.0.0.1:8080/updateUser
http://127.0.0.1:8080/deleteUser
认证是为了保护系统的隐私数据与资源,用户的身份合法方可访问该系统的资源。
认证 :*用户认证就是判断一个用户的身份是否合法的过程*,用户去访问系统资源(url接口)时系统要求验证用户的身份信息,身份合法方可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:用户名密码登录,二维码登录,手机短信登录,指纹认证等方式。
2. Spring Security 简介
官网: https://spring.io/projects/spring-security
中文文档: https://www.springcloud.cc/spring-security.html
2.1 什么是SpringSecurity
Spring Security是一个能够为基于Spring的企业应用系统提供****声明式*****(注解)的安全访问控制解决方案的安全框架*。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
以上解释来源于百度百科。可以一句话来概括,SpringSecurity 是一个安全框架。
3 .使用Security
创建springboot 项目 添加依赖 springsecurity
4 代码结构
启动类
@SpringBootApplication
@EnableWebSecurity // 启动security 5.0后 默认开启,可以不写
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4.1 创建HelloController
@RestController
public class HelloController {
@GetMapping("hello")
public String hello() {
return "hello security";
}
}
http://127.0.0.1:8080/hello
发现我们无法访问hello这个请求,这是因为spring Security默认拦截了所有请求
使用user+启动日志里面的密码登陆
登录后就可以访问我们的hello了
4.2 测试退出
访问: http://localhost:8080/logout
也可以在配置文件配置登录账号和密码
spring:
security:
user:
name: admin #默认使用的用户名
password: 123456 #默认使用的密码
4.3 获取认证信息
我们在HelloController中创建
/*两种获取 用的认证后的信息*/
@RequestMapping("/getUserInfo")
public Principal getUserInfo(Principal principal){
return principal;
}
/*推荐使用 */
@RequestMapping("/getUserInfo2")
public Object getUserInfo2(){
/*从安全上下文中获取 用户 认证对象(用户信息)*/
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
return principal;
// Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// return authentication;
获取到一串json字符串
5 配置多用户
5.1 创建 SecurityConfig 配置类 继承 WebSecurityConfigurerAdapter
重写两个方法 configure(AuthenticationManagerBuilder auth) 和configure(HttpSecurity http)
package com.powernode.config;
@Configuration //创建认证配置类 继承 WebSecurityConfigurerAdapter
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//重写 configure 方法
/*配置多用户*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// super.configure(auth);
/*配置在内存中的 账号密码 不配置可能会配置不成功*/
auth.inMemoryAuthentication()
.withUser("admin")//账号
.password(passwordEncoder().encode("123456"))//密码,使用加密器转码
.roles("ADMIN")//角色,权限
.authorities("sys:add","sys:delete","sys:update","sys:query","sys:export")//权限,(好比 给人钥匙)
.and()
.withUser("test")
.password(passwordEncoder().encode("123456"))
.roles("TEST")
.authorities("sys:add","sys:query")
.and()
.withUser("powernode")
.password(passwordEncoder().encode("123456"))
.roles("VIP")
;
}
/*http请求配置 给请求加权限, 没有加权限的都可以访问 无权限默认访问error目录下的403.html*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http);// 不使用父类的 方法, 需要 提供登录配置
http.formLogin().successForwardUrl("/welcome").failureForwardUrl("/fail");
/*设置 资源所需要的 权限 (好比 门上锁)*/
http.authorizeRequests()
//匹配资源与权限 资源 权限(角色)
.antMatchers("/add").hasAnyAuthority("sys:add")
.antMatchers("/delete").hasAnyAuthority("sys:delete")
.antMatchers("/update").hasAnyAuthority("sys:update")
.antMatchers("/select").hasAnyAuthority("sys:query")
.antMatchers("/export").hasAnyAuthority("sys:export")
//给 父路径下 其所有资源 配置 权限 (觉得)
.antMatchers("/vip/**").hasRole("VIP")
;
}
/*强制要求配置 密码加密器*/
@Bean// 将对象 交给 spring容器 管理
public PasswordEncoder passwordEncoder() {
// return NoOpPasswordEncoder.getInstance();//不加密
return new BCryptPasswordEncoder();
}
/*测试加密器*/
public static void main(String[] args) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
/*加密*/
String encode1 = passwordEncoder.encode("123456");
String encode2 = passwordEncoder.encode("123456");
String encode3 = passwordEncoder.encode("123456");
/*每次 加密的结果不一样*/
System.out.println(encode1);
System.out.println(encode2);
System.out.println(encode3);
/*解密:匹配 密码是否一致 不能 使用 equals方法*/
System.out.println(passwordEncoder.matches("123456", encode1));
System.out.println(passwordEncoder.matches("123456", encode2));
System.out.println(passwordEncoder.matches("123456", encode3));
}
}
5.2 使用注解改进代码
只需要在controller对应的方法上添加注解即可了,不需要再webSecurityConfig中配置匹配的url和权限了,这样就爽多了
*@PreAuthorize 在方法调用前进行权限检查*
@PostAuthorize 在方法调用后进行权限检查
上面的两个注解如果要使用的话必须加上
*@EnableGlobalMethodSecurity(prePostEnabled = true)*
controller 层
6 创建各种处理器
6.1 无权限处理器
/*
没有权限处理器
* */
@Component
public class AppAccessDeniedHandler implements AccessDeniedHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
System.out.println("没有权限");
/*设置响应编码 放置乱码*/
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=utf-8");
/*创建统一返回类型对象*/
Result result = new Result();
result.setCode(403);
result.setMsg("没有权限");
/*将 result 转换为 json数据*/
String json = objectMapper.writeValueAsString(result);
/*响应json数据*/
PrintWriter writer = response.getWriter();
writer.write(json);
writer.flush();
}
}
6.2 登录失败处理器
/*登录失败处理器*/
@Component
public class AppAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
System.out.println("登录失败");
/*设置响应编码 放置乱码*/
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=utf-8");
/*创建统一返回类型对象*/
Result result = new Result();
result.setCode(-1);
result.setMsg("登录失败");
if (e instanceof BadCredentialsException) {
result.setData("失败原因:密码不正确");
} else if (e instanceof DisabledException) {
result.setData("失败原因:账号被禁用");
} else if (e instanceof UsernameNotFoundException) {
result.setData("失败原因:查无此账号");
} else if (e instanceof CredentialsExpiredException) {
result.setData("失败原因:密码过期");
} else if (e instanceof AccountExpiredException) {
result.setData("失败原因:账号过期");
} else if (e instanceof LockedException) {
result.setData("失败原因:账号被锁定");
}
/*将 result 转换为 json数据*/
String json = objectMapper.writeValueAsString(result);
/*响应json数据*/
PrintWriter writer = response.getWriter();
writer.write(json);
writer.flush();
}
}
6.3 登录成功处理器
/*
* 自定义的 登录成功处理器 直接返回json数据
* */
@Component
public class AppAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
private ObjectMapper objectMapper;
/**
* @param request 请求对象
* @param response 响应对象
* @param authentication 认证对象(用户信息)
* @throws IOException
* @throws ServletException
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("登录成功");
/*设置响应编码 放置乱码*/
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=utf-8");
/*创建统一返回类型对象*/
Result result = new Result();
result.setCode(200);
result.setMsg("登录成功");
result.setData(authentication);//只是为了显示 用户信息 实际情况不会返回
/*将 result 转换为 json数据*/
String json = objectMapper.writeValueAsString(result);
/*响应json数据*/
PrintWriter writer = response.getWriter();
writer.write(json);
writer.flush();
}
}
6.4 登出成功处理器
/*登出成功处理器*/
@Component
public class AppLogoutSuccessHandler implements LogoutSuccessHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("登出成功");
/*设置响应编码 放置乱码*/
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=utf-8");
/*创建统一返回类型对象*/
Result result = new Result();
result.setCode(200);
result.setMsg("登出成功");
result.setData(authentication);//只是为了显示 用户信息 实际情况不会返回
/*将 result 转换为 json数据*/
String json = objectMapper.writeValueAsString(result);
/*响应json数据*/
PrintWriter writer = response.getWriter();
writer.write(json);
writer.flush();
}
}
7 配置类
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/*注入 登录成功处理器*/
@Autowired
private AppAuthenticationSuccessHandler appAuthenticationSuccessHandler;
/*注入 登录 失败处理器*/
@Autowired
private AppAuthenticationFailureHandler appAuthenticationFailureHandler;
/*注入 没有权限处理器*/
@Autowired
private AppAccessDeniedHandler appAccessDeniedHandler;
/*注入 登出成功处理器*/
@Autowired
private AppLogoutSuccessHandler appLogoutSuccessHandler;
@Autowired
private AppUserDetailsService appUserDetailsService;
//验证码拦截器注入
@Autowired
private ValidateCodeFilter validateCodeFilter;
/*配置多用户*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(appUserDetailsService);
}
/*http请求配置*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http);// 不使用父类的 方法, 需要 提供登录配置
/*没权权限的处理*/
// http.exceptionHandling().accessDeniedHandler(appAccessDeniedHandler);
/*登录*/
// 配置登录之前添加一个验证码的过滤器
http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);
http.formLogin()
.usernameParameter("uname")//页面表单账号的参数名 默认 为 username
.passwordParameter("pwd")//页面表单密码的参数名 默认 为 password
.loginPage("/index/toLogin")//定义登录页面的 请求 地址(转发到登录页面)
.loginProcessingUrl("/login/doLogin")// 表单提交的 地址(不需要提供),登录验证.....
.successForwardUrl("/index/toIndex")//登录成功 跳转的路径
.failureForwardUrl("/index/toLogin")//登录失败 跳转的路径
// .successHandler(appAuthenticationSuccessHandler)//登录成功处理器
// .failureHandler(appAuthenticationFailureHandler)//登录失败处理器
.permitAll();
;
/*登出*/
http.logout()
.logoutUrl("/logout")//登出的 请求地址
.logoutSuccessUrl("/index/toLogin")//登出成功后 访问的路径
// .logoutSuccessHandler(appLogoutSuccessHandler)//登出成功处理器
.permitAll()
;
/*设置 资源所需要的 权限 (好比 门上锁)*/
http.authorizeRequests()
.antMatchers("/code/img")// 放行验证码的路径
// .mvcMatchers("/index/toLogin", "/index.html").permitAll()//不需要认证就可以访问
.permitAll()
.anyRequest().authenticated()//所有请求都需要登录认证 才能进行
;
/*禁用csrf跨域请求攻击 如果不禁用 自定义的登录页面无法登录*/
http.csrf().disable();
}
/*资源服务匹配放行:静态资源*/
@Override
public void configure(WebSecurity web) throws Exception {
// super.configure(web);
web.ignoring().antMatchers("/css/**");
}
/*强制要求配置 密码加密器*/
@Bean// 将对象 交给 spring容器 管理
public PasswordEncoder passwordEncoder() {
// return NoOpPasswordEncoder.getInstance();//不加密
return new BCryptPasswordEncoder();
}
}
8. 配置多用户(从数据库中获取)
/*
* 自定义认证
*
* */
@Component
public class AppUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private SysUserDao sysUserDao;
/**
* @param username 用户输入的 账号
* @return 用户信息(账号密码 权限,是否过期等 方法)
* @throws UsernameNotFoundException 没有账号会抛该异常类型
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
/*通过账号查询用户*/
SysUser sysUser = sysUserDao.queryUserByUsername(username);
if (sysUser == null) {
throw new UsernameNotFoundException("用户名不存在");
}
/*根据用户的id 获取用户的 权限字符串 集合*/
List<String> permissions = sysUserDao.queryPermissionByUserId(sysUser.getUserId());
/*将 字符串集合 转换为 权限对象集合*/
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (String permission : permissions) {
/*根据权限字符串 生成 对应的 权限对象*/
authorities.add(new SimpleGrantedAuthority(permission));
}
/*将权限集合封装到 user对象中*/
sysUser.setPermissions(authorities);
return sysUser;
}
}
9 sysuser实体类
/**
* (SysUser)实体类
* 实现序列化接口和 UserDetails接口
*
* @author makejava
* @since 2022-10-25 15:32:59
*/
public class SysUser implements Serializable, UserDetails {
private static final long serialVersionUID = -54448691274655158L;
/**
* 编号
*/
private Integer userId;
/**
* 登陆名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 性别
*/
private String sex;
/**
* 地址
*/
private String address;
/**
* 是否启动账户0禁用 1启用
*/
private Integer enabled;
/**
* 账户是否没有过期0已过期 1 正常
*/
private Integer accountNoExpired;
/**
* 密码是否没有过期0已过期 1 正常
*/
private Integer credentialsNoExpired;
/**
* 账户是否没有锁定0已锁定 1 正常
*/
private Integer accountNoLocked;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Integer getEnabled() {
return enabled;
}
public void setEnabled(Integer enabled) {
this.enabled = enabled;
}
public Integer getAccountNoExpired() {
return accountNoExpired;
}
public void setAccountNoExpired(Integer accountNoExpired) {
this.accountNoExpired = accountNoExpired;
}
public Integer getCredentialsNoExpired() {
return credentialsNoExpired;
}
public void setCredentialsNoExpired(Integer credentialsNoExpired) {
this.credentialsNoExpired = credentialsNoExpired;
}
public Integer getAccountNoLocked() {
return accountNoLocked;
}
public void setAccountNoLocked(Integer accountNoLocked) {
this.accountNoLocked = accountNoLocked;
}
@Override//是1返回true 不是就返回false
public boolean isAccountNonExpired() {
return accountNoExpired.equals(1);
}
@Override
public boolean isAccountNonLocked() {
return accountNoLocked.equals(1);
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNoExpired.equals(1);
}
@Override
public boolean isEnabled() {
return enabled.equals(1);
}
/*权限集合*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return permissions;
}
/*封装权限集合*/
private List<SimpleGrantedAuthority> permissions = new ArrayList<>();
public List<SimpleGrantedAuthority> getPermissions() {
return permissions;
}
public void setPermissions(List<SimpleGrantedAuthority> permissions) {
this.permissions = permissions;
}
}