Security(安全框架)

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;
    }

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

huangshaohui00

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值