[关闭]
@wangzhuanyun 2020-09-22T07:29:21.000000Z 字数 7167 阅读 1341

Spring Security (七)

springboot


Spring Security是什么?

Spring Security 提供了基于javaEE的企业应有个你软件全面的安全服务。这里特别强调支持使用SPring框架构件的项目,Spring框架是企业软件开发javaEE方案的领导者。如果你还没有使用Spring来开发企业应用程序,我们热忱的鼓励你仔细的看一看。熟悉Spring特别是一来注入原理两帮助你更快更方便的使用Spring Security

正如你可能知道的两个应用程序的两个主要区域是“认证”和“授权”(或者访问控制)。这两个主要区域是Spring Security 的两个目标。“认证”,是建立一个他声明的主题的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统)。“授权”指确定一个主体是否允许在你的应用程序执行一个动作的过程。为了抵达需要授权的店,主体的身份已经有认证过程建立。这个概念是通用的而不只在Spring Security中

spring security 的核心功能主要包括:

  1. 认证 (你是谁)
  2. 授权 (你能干什么)
  3. 攻击防护 (防止伪造身份)

认证(验证)就是确认用户的身份,一般采用用户名和密码的形式;授权(鉴权)就是确认用户拥有的身份(角色、权限)能否访问受保护的资源

初次体验

我们创建一个 HelloController:

  1. @RestController
  2. public class HelloController {
  3. @GetMapping("/hello")
  4. public String hello() {
  5. return "hello";
  6. }
  7. }

访问 /hello ,需要登录之后才能访问。
此处输入图片的描述

默认用户名为user 密码则是项目启动时随机生成的字符串,可以从启动的控制台日志中看到默认密码
此处输入图片的描述

这个随机生成的密码,每次启动时都会变。对登录的用户名/密码进行配置,有三种不同的方式:

  1. 在 application.properties 中进行配置
  2. 通过 Java 代码配置在内存中
  3. 通过 Java 从数据库中加载

配置文件配置用户名/密码

可以直接在 application.properties 文件中配置用户的基本信息:

  1. spring.security.user.name=user
  2. spring.security.user.password=123

配置完成后,重启项目,就可以使用这里配置的用户名/密码登录了。

Java 配置用户名/密码

也可以在 Java 代码中配置用户名密码,首先需要我们创建一个 Spring Security 的配置类,继承自 WebSecurityConfigurerAdapter 类,重写configure方法

  1. @Configuration
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  4. //配置用户名 密码 及用户角色(角色目前无用)
  5. auth.inMemoryAuthentication().passwordEncoder(passwordEncoder()).withUser("wzy").
  6. password(passwordEncoder().encode("123")).roles("USER");
  7. }
  8. @Bean
  9. PasswordEncoder passwordEncoder() {
  10. return new BCryptPasswordEncoder();
  11. }
  12. }

从 Spring5 开始,强制要求密码要加密,如果非不想加密,可以使用一个过期的 PasswordEncoder 的实例 NoOpPasswordEncoder,但是不建议这么做,毕竟不安全。
Spring Security 中提供了 BCryptPasswordEncoder 密码编码工具,可以非常方便的实现密码的加密加盐,相同明文加密出来的结果总是不同,这样就不需要用户去额外保存盐的字段。

再次登录输入设置的用户名wzy与密码123即可登录。

登录验证连接数据库

创建LoginAuthenticationProvider类,实现接口AuthenticationProvider

  1. @Component
  2. public class LoginAuthenticationProvider implements AuthenticationProvider {
  3. @Override
  4. public Authentication authenticate(Authentication authentication) throws AuthenticationException {
  5. String userName = (String) authentication.getPrincipal(); //拿到username
  6. String password = (String) authentication.getCredentials(); //拿到password
  7. System.out.println("userName========"+userName);
  8. System.out.println("password========"+password);
  9. //调用service层 连接数据库 返回登录对象 此处忽略
  10. SfAdmin sfAdmin = new SfAdmin();
  11. sfAdmin.setAdmin_pwd(password);
  12. sfAdmin.setAdmin_name(userName);
  13. //判断是否登录 未登录不进入
  14. if(sfAdmin!=null)
  15. {
  16. //设置的权限必须以 ROLE开头
  17. Collection<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN");
  18. return new UsernamePasswordAuthenticationToken(sfAdmin, sfAdmin.getAdmin_pwd(),authorities);
  19. }
  20. /*验证不通过*/
  21. return null;
  22. }
  23. /**
  24. * 如果该AuthenticationProvider支持传入的Authentication对象,则返回true
  25. * @param aClass
  26. * @return
  27. */
  28. @Override
  29. public boolean supports(Class<?> aClass) {
  30. return aClass.equals(UsernamePasswordAuthenticationToken.class);
  31. }
  32. }

修改SecurityConfig配置类

  1. @Configuration
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3. //实例化过滤器
  4. @Autowired
  5. private LoginAuthenticationProvider provider;
  6. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  7. //登录调用provider
  8. auth.authenticationProvider(provider);
  9. }
  10. @Override
  11. protected void configure(HttpSecurity http) throws Exception {
  12. http
  13. // 关闭csrf防护
  14. .csrf().disable()
  15. .headers().frameOptions().disable()
  16. .and();
  17. http
  18. //登录处理
  19. .formLogin() //表单方式,或httpBasic
  20. .loginPage("/login.html") //自定义登录页面
  21. .loginProcessingUrl("/form") //表单提交
  22. .defaultSuccessUrl("/admin/hello") //成功登陆后跳转页面
  23. .permitAll(); //放行以上路径
  24. http.authorizeRequests()
  25. .antMatchers("/").permitAll() //放行这个请求
  26. .antMatchers("/admin/**").hasRole("ADMIN") //admin下的页面需要ADMIN这个角色才可以访问
  27. .antMatchers("/user/**").hasRole("USER");
  28. }

编写Controller进行测试

  1. @Controller
  2. @RequestMapping("/admin")
  3. public class IndexController {
  4. @RequestMapping("/hello")
  5. public String hello(){
  6. return "hello";
  7. }
  8. }
  9. @Controller
  10. @RequestMapping("/user")
  11. public class UserController {
  12. @RequestMapping("/hello")
  13. public String security(){
  14. return "hello";
  15. }
  16. }

前后端分类实现登录鉴权

创建类进行登录拦截RbacAuthorityService

  1. @Component("rbacauthorityservice")
  2. public class RbacAuthorityService {
  3. @Resource
  4. private Environment env;
  5. @Resource
  6. private StringRedisTemplate stringRedisTemplate;
  7. /**
  8. * 返回false时 调用AjaxAuthenticationEntryPoint类
  9. * @param request
  10. * @param authentication
  11. * @return
  12. */
  13. public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
  14. // 获取当前请求的URI
  15. String requestURI = request.getServletPath();
  16. // 放开登录url
  17. if (requestURI.equals("/admin/login")){
  18. return true;
  19. }
  20. // 登录判断
  21. String token = request.getHeader("userToken");
  22. //token需要到redis中找 找不到也是未登录
  23. System.out.println(token);
  24. if (token == null ){
  25. request.setAttribute("flagName","未登录");
  26. return false;
  27. }
  28. // 权限判断 利用token获取用户登录对象 获取角色ID 通过角色ID到redis中找角色对应的权限集合
  29. // 利用token去Redis取出当前角色的权限,这里就直接写死了
  30. List<String> roles = new ArrayList<>();
  31. roles.add("/admin/list");
  32. roles.add("/admin/menu");
  33. if (!roles.contains(requestURI)){
  34. request.setAttribute("flagName","权限不足");
  35. return false;
  36. }
  37. return true;
  38. }
  39. }

创建AjaxAuthenticationEntryPoint类实现AuthenticationEntryPoint接口,统一返回JSON给前端

  1. /**
  2. * 用户没有登录时返回给前端的数据
  3. */
  4. @Component
  5. public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {
  6. @Override
  7. public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
  8. String msgName = httpServletRequest.getAttribute("msgName").toString();
  9. MassageJson massageJson = new MassageJson();
  10. if (msgName.equals("未登录")){
  11. massageJson.setCode(001);
  12. massageJson.setMsg("未登录");
  13. } else if (msgName.equals("权限不足")){
  14. massageJson.setCode(002);
  15. massageJson.setMsg("权限不足");
  16. } else{
  17. massageJson.setCode(003);
  18. massageJson.setMsg("系统异常");
  19. }
  20. httpServletResponse.setCharacterEncoding("utf-8");
  21. httpServletResponse.getWriter().write(JSON.toJSONString(massageJson));
  22. }
  23. }

修改配置类SecurityConfig

  1. @Configuration
  2. @EnableWebSecurity
  3. @EnableGlobalMethodSecurity(prePostEnabled = true)
  4. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  5. /**
  6. * 未登陆时返回 JSON 格式的数据给前端
  7. */
  8. @Resource
  9. AjaxAuthenticationEntryPoint ajaxAuthenticationEntryPoint;
  10. @Override
  11. protected void configure(HttpSecurity http) throws Exception {
  12. // 去掉 CSRF
  13. http.csrf().disable()
  14. .exceptionHandling().authenticationEntryPoint(ajaxAuthenticationEntryPoint)
  15. .and()
  16. // 基于Token 不需要Session
  17. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  18. // 登录处理
  19. .and()
  20. .formLogin()
  21. .loginProcessingUrl("admin/login")
  22. .permitAll()
  23. // 登录和权限控制
  24. .and()
  25. .authorizeRequests()
  26. .anyRequest()
  27. // RBAC 动态 url 认证
  28. .access("@rbacauthorityservice.hasPermission(request,authentication)");
  29. }
  30. }

添加注销功能

创建AjaxLogoutSuccessHandler类实现接口LogoutSuccessHandler,进行注销

  1. /**
  2. * 退出登录
  3. */
  4. @Component
  5. public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {
  6. @Resource
  7. private StringRedisTemplate stringRedisTemplate;
  8. @Override
  9. public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
  10. // 从这里拿到token 然后把这个token注销
  11. String token = httpServletRequest.getHeader("userToken");
  12. // 去redis删除token
  13. stringRedisTemplate.delete(token);
  14. MassageJson massageJson = new MassageJson();
  15. massageJson.setCode(004);
  16. massageJson.setMsg("退出成功!");
  17. httpServletResponse.setContentType("text/html;charset=utf-8");
  18. httpServletResponse.getWriter().write(JSON.toJSONString(massageJson));
  19. }
  20. }

修改SecurityConfig配置类

  1. /**
  2. * 注销成功返回的 JSON 格式数据给前端
  3. */
  4. @Resource
  5. AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;
  6. //在configure方法下添加 注销处理
  7. .and()
  8. .logout()//默认注销行为为logout
  9. .logoutUrl("/admin/loginOut") //设置调用路径
  10. .logoutSuccessHandler(ajaxLogoutSuccessHandler)
  11. .permitAll();
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注