springsecurity Spring-Security

spring spring-security概述

  1. 认证(你是谁,户/设备/系统
  2. 验证(你能干什么,也叫权限控制/授权,允许执行的操作)
  3. 基于Filter , Servlet, AOP实现身份认证和权限验证
实例驱动学习入门案例1、新建工程 , 导入依赖
<!--spring-security相关依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>2、配置访问地址 , 启动测试 , 获得秘钥
登陆名:user
密码:查看日志
【springsecurity Spring-Security】
springsecurity Spring-Security

文章插图
3、小结
使用AOP做了拦截 , 拦截后再访问的servlet
security相关配置自定义用户名和密码在Application.yml 中配置文件上的security的user和password
spring:security:user:name: adminpassword: admin关闭验证在启动类的注解中 , 排除安全验证 , 使用内存中的用户信息
springsecurity Spring-Security

文章插图
使用内存中的账户使用类WebSecurityConfigurerAdapter控制安全管理内容
1. 自定义类继承WebSecurityConfigurerAdapter , 声明是个配置类、开启WebSecurity2. 重写configure方法3. 在SpringSecurity_v5.0版本中 , 密码必须使用一个加密方式4. 构建一个方法创建一个BCrypt加密类 , 用于加密操作(BCrypt跨平台)@Configuration@EnableWebSecuritypublic class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 得到一个加密类 , PasswordEncoder passwordEncoder = passwordEncoder();auth.inMemoryAuthentication().withUser("zhangsan").password(passwordEncoder.encode("zhangsan")).roles();auth.inMemoryAuthentication().withUser("lisi").password(passwordEncoder.encode("lisi")).roles();auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder.encode("admin")).roles();}// 构建一个方法创建一个加密类 , 放入容器中@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}}使用数据库的账户1、导入依赖
<!--mysql驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.23</version></dependency><!--spring-jpa依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId><version>2.4.5</version></dependency>2、封装对象 , 创建一个实体类 , 初始化数据 使用JPA连接数据库
3.1、创建一个实体类
package com.study.entity;import lombok.Data;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;@Entity@Datapublic class UserInfo {@Id@GeneratedValue(strategy = GenerationType.AUTO)private long id;private String username;private String password;private String role;}3.2、创建一个dao
package com.study.dao;import com.study.entity.UserInfo;import org.springframework.data.jpa.repository.JpaRepository;public interface UserInfoDao extends JpaRepository<UserInfo, Long> {UserInfo findByUsername(String name);}3.3、创建service层的类 接口和实现类
package com.study.service;import com.study.entity.UserInfo;public interface UserInfoService {UserInfo findUserInfo(String username);}package com.study.service.impl;import com.study.dao.UserInfoDao;import com.study.entity.UserInfo;import com.study.service.UserInfoService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class UserInfoServiceImpl implements UserInfoService {@AutowiredUserInfoDao userInfoDao;public UserInfo findUserInfo(String username) {UserInfo userInfo = userInfoDao.findByUsername(username);return userInfo;}}3.4、配置数据库连接信息
spring.datasource.url=jdbc:mysql://8.129.121.241:3306/springsecurity?serverTimezone=GMT%2B8&characterEncoding=utf8spring.datasource.username=xxyyspring.datasource.password=xxYY11..spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver#spring-data-jpaspring.jpa.generate-ddl=truespring.jpa.show-sql=truespring.jpa.database=mysql3.5、初始化数据库数据
package com.study.init;import com.study.dao.UserInfoDao;import com.study.entity.UserInfo;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;@Componentpublic class JdbcInit {@Autowiredprivate UserInfoDao userInfoDao;@PostConstructpublic void init() {PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();UserInfo userInfo = new UserInfo();userInfo.setUsername("lisi");userInfo.setPassword(passwordEncoder.encode("lisi"));userInfo.setRole("normal");userInfoDao.save(userInfo);}}3.6、查询数据库 , 构造一个User对象 , 用于框架中使用
package com.study.provider;import com.study.dao.UserInfoDao;import com.study.entity.UserInfo;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.User;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.Service;import java.util.ArrayList;import java.util.List;@Servicepublic class MyUserDetailService implements UserDetailsService {@Autowiredprivate UserInfoDao userInfoDao;public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user=null;UserInfo userInfo=null;if(username!=null){userInfo = userInfoDao.findByUsername(username);if (userInfo!=null){List<GrantedAuthority> list=new ArrayList<GrantedAuthority>();GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_"+userInfo.getRole());list.add(authority);user=new User(userInfo.getUsername(),userInfo.getPassword(),list);}}return user;}}4、编写配置类 , 将通过数据库得到的User对象 , 进行角色配置
package com.study.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());}}5、添加Controller , 最后测试验证 , 注意将添加数据库信息的注解注释掉
package com.study.controller;import org.springframework.security.access.prepost.PreAuthorize;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class HelloController {@GetMapping(value = "https://tazarkount.com/hello")public String hello() {return "hello spring-security";}@RequestMapping(value = "https://tazarkount.com/hellouser")@PreAuthorize(value = "https://tazarkount.com/read/hasAnyRole('ROLE_admin','ROLE_normal')")public String helloUser() {return "hello spring-security have normail Admin role";}@RequestMapping(value = "https://tazarkount.com/helloadmin")@PreAuthorize(value = "https://tazarkount.com/read/hasAnyRole('ROLE_admin')")public String helloAdmin() {return "hello spring-security have Admin role";}}踩坑所有的权限都要加上 “ROLE_” 作为前缀
基于角色权限认证和授权
  1. authentication:认证 , 认证访问者是谁 。一 个用户或者一个其他系统是不是当前要访问的系统中的有效用户 。
  2. authorization:授权 , 访问者能做什么?
    比如说张三用户要访问一个公司OA系统 。首先系统要判断张三 是不是公司中的有效用户
  3. 例如:认证:张三是不是有效的用户 , 是不是公司的职员
    授权:判断张三能否做某些操作 , 如果张三是个领导可以批准下级的请假 , 其他的操作
RBAC是什么?
springsecurity Spring-Security

文章插图
  1. 创建 Handler 实现两个不同接口
  • MyFailureHandler
@Component("myFailureHandler")public class MyFailureHandler implements AuthenticationFailureHandler {/*参数:request : 请求对象response:应答对象authentication: spring security框架验证用户信息成功后的封装类 。*/@Overridepublic void onAuthenticationFailure(HttpServletRequest request,HttpServletResponse response,AuthenticationException e) throws IOException {//当框架验证用户信息失败时执行的方法response.setContentType("text/json;charset=utf-8");PrintWriter writer = response.getWriter();writer.println("{\"msg\":\"登陆失败!\"}");writer.flush();writer.close();}}
  • MySuccessHandler
@Component("mySuccessHandler")public class MySuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {response.setContentType("text/json;charset=utf-8");PrintWriter pw = response.getWriter();pw.write("{\"msg\":\"登陆成功!\"}");pw.flush();pw.close();}}
  1. 配置文件添加自己创建的handler
@Configuration@EnableWebSecuritypublic class CustomSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate AuthenticationFailureHandler myFailureHandler;@Autowiredprivate AuthenticationSuccessHandler mysuccessHandler;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/index","/mylogin.html","/login","/js/**").permitAll()//将要用到的地址排除.antMatchers("/access/user/**").hasRole("USER").antMatchers("/access/read/**").hasRole("READ").antMatchers("/access/admin/**").hasRole("ADMIN").and().formLogin().successHandler(mysuccessHandler)//的定义登陆成功Handler.failureHandler(myFailureHandler)//的定义登陆失败Handler.loginPage("/myajax.html")//登陆的自定义页面.loginProcessingUrl("/login")//登陆时提交的地址.and().csrf().disable();//将跨域问题禁用}}使用Json处理数据
  1. 导入依赖
    使用 SpringBoot 就不要添加版本号 , 否则会版本冲突
  2. 返回结果
@Data@AllArgsConstructor@NoArgsConstructorpublic class Result {// 0 成功 , 1 失败private int code;//表示错误码private int error;//消息文本private String msg;}
  1. 使用 outputStream 输出数据
response.setContentType("text/json;charset=utf-8");Result result = new Result();result.setCode(1);result.setError(1001);result.setMsg("登陆失败");ObjectMapper objectMapper = new ObjectMapper();ServletOutputStream outputStream = response.getOutputStream();objectMapper.writeValue(outputStream, result);outputStream.flush();outputStream.close();验证码
  1. 因为使用的是 SpringSecurity  , 所以将验证码 url 添加到白名单
  2. 代码
@RestController@RequestMapping("/captcha")public class CaptChaController {//定义一个值 , 用来生成验证码的图片//图像宽度 120像素private int width = 120;//图像高度 30 像素private int height = 30;//图片内容在图片的起始位置 12像素private int drawY = 20;//文字的间隔18像素private int space = 15;//验证码有个文字private int charCount = 6;//验证码的内容数组private String chars[] = {"A", "B", "C", "D", "E", "F","G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "T", "U", "V", "W","X", "Y", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"};//定义方法:生成验证码内容 。在一个图片上 , 写入文字@GetMapping("/code")public void makeCaptchaCode(HttpServletRequest request, HttpServletResponse response) throws IOException {/*验证码:需要在内存中绘制一个图片BufferedImage.向这个图片中写入文字 。把绘制好内容的图片响应给请求*///1.创建一个背景透明的图片 , 使用rgb表示颜色的BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);//2.获取画笔Graphics g = image.getGraphics();//3.1设置使用画笔是白颜色g.setColor(Color.white);//3.2给image画板都涂成白色的// fillRect(矩形的起始x , 矩形的起始y ,  矩形的宽度 , 矩形的高度)g.fillRect(0, 0, width, height);//画内容//4.创建一个字体Font font = new Font("宋体", Font.BOLD, 16);g.setFont(font);g.setColor(Color.black);//5.在画布上 , 写一个文字//参数: 文字 , x , y坐标//g.drawString("中",10,drawY);StringBuffer buffer = new StringBuffer("");int ran = 0;int len = chars.length;for (int i = 0; i < charCount; i++) {ran = new Random().nextInt(len);buffer.append(chars[ran]);//绘制文字获取随机颜色g.setColor(makeColor());g.drawString(chars[ran], (i + 1) * space, drawY);}//6.绘制干扰线for (int m = 0; m < 4; m++) {g.setColor(makeColor());int dot[] = makeLineDot();g.drawLine(dot[0], dot[1], dot[2], dot[3]);}//把生成的验证码存储到session中request.getSession().setAttribute("code", buffer.toString());//设置没有缓冲response.setHeader("Pragma", "no-cache");response.setHeader("Cache-Control", "no-cache");response.setDateHeader("Expires", 0);response.setContentType("image/png");OutputStream out = response.getOutputStream();/*RenderedImage im, 输出的图像String formatName, 图像的格式 jpg , jpeg ,  pngImageOutputStream output 输出到哪*/ImageIO.write(image, "png", out);out.flush();out.close();} //取随机颜色private Color makeColor() {Random random = new Random();int r = random.nextInt(255);int g = random.nextInt(255);int b = random.nextInt(255);return new Color(r, g, b);} //设置干扰线private int[] makeLineDot() {Random random = new Random();int x1 = random.nextInt(width / 2);//起点int y1 = random.nextInt(height);//起点int x2 = random.nextInt(width);//终点int y2 = random.nextInt(height);//终点return new int[]{x1, y1, x2, y2};}}
  1. 修改 myajax.html 增加验证码
function changeCode() {//new Date目的是浏览器不使用缓存 , 每次获取新的内容var url="/captcha/code?t="+new Date();$("#imagecode").attr("src",url);}<div >用户名:<input type="text" id="username" value=""> <br/>密&nbsp;&nbsp;码:<input type="text" id="password" value=""> <br/>验证码:<input type="text" id="txtcode" value=""> <br/><!--图像 , 显示验证码的值 --><img id="imagecode" src="https://tazarkount.com/captcha/code" /><a href="javascript:void(0)" onclick="changeCode()">重新获取</a><br/><br/><button id="btnLogin">使用ajax登录</button></div>
  1. 增加验证码参数
$(function () {$("#btnLogin").click(function () {alert("click");var uname = $("#username").val();var upwd = $("#password").val();var txtcode = $("#txtcode").val();$.ajax({url: "/login",type: "POST",data: {"username": uname,"password": upwd,"code": txtcode},dataType: "json",success: function (resp) {alert(resp.msg)}})})})过滤器进行验证Code
springsecurity Spring-Security

文章插图
  1. 使用的是过滤器 , 整个 Spring Security 框架就是一个过滤器实现的
  2. 用户发起请求 , 使用的过滤器:UsernamePasswordAuthenticationFilter
  3. 验证 username 和 password 之前 , 就应该先验证 code 是否正确 , 在UsernamePasswordAuthenticationFilter之前增加一个自定义过滤器 , 验证 session 中的 Code  , 如果验证失败 , 排除异常
    1. 直接实现 Filter 接口
    2. 继承只执行一次的过滤器
创建一个异常类
  1. 创建异常类继承 AuthenticationException
public class VerificationException extends AuthenticationException {public VerificationException(String msg, Throwable cause) {super(msg, cause);}public VerificationException(String msg) {super(msg);}public VerificationException() {super("验证错误 , 请重新输入");}}
  1. 创建过滤器类继承 OncePerRequestFilter
public class VerificationCodeFilter extends OncePerRequestFilter {private MyFailureHandler failureHandler = new MyFailureHandler();@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {String uri = request.getRequestURI();if (!("/login".equals(uri))) {filterChain.doFilter(request, response);} else {try {verifcatioinCode(request);filterChain.doFilter(request, response);} catch (VerificationException e) {Result result = new Result();result.setCode(1);result.setError(1002);result.setMsg("验证码错误!!!");failureHandler.setResult(result);failureHandler.onAuthenticationFailure(request, response, e);}}}private void verifcatioinCode(HttpServletRequest request) {HttpSession session = request.getSession();String requestCode = request.getParameter("code");Object attrCode = session.getAttribute("code");String sessionCode = "";if (attrCode != null) {sessionCode = (String) attrCode;}System.out.println("VerificationCodeFilterdoFilterInternal requestCode:" + requestCode + "|sessionCode:" + sessionCode);//if (!StringUtils.isEmpty(sessionCode)) {////session中存在code , 就清除//session.removeAttribute("code");//}if (StringUtils.isEmpty(requestCode) || StringUtils.isEmpty(sessionCode) || !requestCode.equals(sessionCode)) {throw new VerificationException();}}}
  1. 自定义过滤器添加到过滤器链中
@Configuration@EnableWebSecuritypublic class CustomSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate AuthenticationFailureHandler myFailureHandler;@Autowiredprivate AuthenticationSuccessHandler mysuccessHandler;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/index", "/mylogin.html", "/login", "/js/**", "/login.jsp").permitAll()//将要用到的地址排除.antMatchers("/access/user/**").hasRole("USER").antMatchers("/access/read/**").hasRole("READ").antMatchers("/access/admin/**").hasRole("ADMIN").and().formLogin().successHandler(mysuccessHandler).failureHandler(myFailureHandler).loginPage("/myajax.html")//登陆的自定义页面.loginProcessingUrl("/login")//登陆时提交的地址.and().csrf().disable()//将跨域问题禁用//自定义过滤器添加到过滤器链中.addFilterBefore(new VerificationCodeFilter(), UsernamePasswordAuthenticationFilter.class);}}