动力节点Springsecurity笔记06-13基于数据库的方法授权

news2024/12/23 11:14:32

6 密码处理

6.1 为什么要加密?

csdn 密码泄露事件
泄露事件经过:https://www.williamlong.info/archives/2933.html
泄露数据分析:https://blog.csdn.net/crazyhacking/article/details/10443849

6.2加密方案

密码加密一般使用散列函数,又称散列算法,哈希函数,这些函数都是单向函数(从明文到密文,反之不行)
常用的散列算法有MD5和SHA
Spring Security提供多种密码加密方案,基本上都实现了PasswordEncoder接口,官方推荐使用BCryptPasswordEncoder

6.3 BCryptPasswordEncoder类初体验

拷贝springsecurity-04-inmemory工程,重命名为springsecurity-05-password-encode
test/java 下新建包com.powernode.password,在该包下新建测试类PasswordEncoderTest,如下

@Slf4j
  public class PasswordEncoderTest {
    @Test
    @DisplayName("测试加密类BCryptPasswordEncoder")
    void testPassword(){
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        //加密(明文到密文)
        String encode1 = bCryptPasswordEncoder.encode("123456");
        _log_.info("encode1:"+encode1);
        String encode2 = bCryptPasswordEncoder.encode("123456");
        _log_.info("encode2:"+encode2);
        String encode3 = bCryptPasswordEncoder.encode("123456");
        _log_.info("encode3:"+encode3);
        //匹配方法,判断明文经过加密后是否和密文一样
        boolean result1 = bCryptPasswordEncoder.matches("123456", encode1);
        boolean result2 = bCryptPasswordEncoder.matches("123456", encode1);
        boolean result3 = bCryptPasswordEncoder.matches("123456", encode1);
        _log_.info(result1+":"+result2+":"+result3);
        _assertTrue_(result1);
        _assertTrue_(result2);
        _assertTrue_(result3);
    }
} 

查看控制台发现特点是:**相同的字符串加密之后的结果都不一样,但是比较的时候是一样的,因为加了盐(**salt)了。

上面简单看下即可
小提示:
Ø 开发代码时不允许使用main方法测试,而是使用单元测试来测试
Ø 代码中一般不允许使用System.out.println 直接输出,而是使用日志输出
Ø 单元测试尽量使用断言,而不是使用System.out.println输出

6.4 使用加密器并且加密

修改MySecurityUserConfig类中的加密器bean

@Bean

  public PasswordEncoder passwordEncoder(){
    //使用加密算法对密码进行加密
    return new BCryptPasswordEncoder();
  } 

启动程序测试,发现不能正常登录
原因是输入的密码是进行加密了,但是系统中定义的用户密码没有加密
将系统定义的用户密码修改成密文,如下

@Configuration
  public class MySecurityUserConfig {
    @Bean
    public UserDetailsService userDetailService() {
  //        使用org.springframework.security.core.userdetails.User类来定义用户
        //定义两个用户
        UserDetails user1 = User._builder_()
                .username("eric")
                .password(passwordEncoder().encode("123456"))
                .roles("student")
                .build();
        UserDetails user2 = User._builder_()
                .username("thomas")
                .password(passwordEncoder().encode("123456"))
                .roles("teacher")
                .build();
        //创建两个用户
        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
        userDetailsManager.createUser(user1);
        userDetailsManager.createUser(user2);
        return userDetailsManager;
    }
    /*
     * 从 Spring5 开始,强制要求密码要加密
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        //使用加密算法对密码进行加密
        return new BCryptPasswordEncoder();
    }
  
} 

重启程序,再次测试即可,发现登录和访问没问题了

7 查看当前登录用户信息及配置用户权限

复制springsecurity-05-password-encode,复制后为springsecurity-06-loginuser-info

7.1 获取当前登录用户信息

新建一个controller

@RestController
  public class CurrentLoginUserInfoController { 
    _/**
     * 从当前请求对象中获取
     */
    _@GetMapping("/getLoginUserInfo")
    public Principal getLoginUserInfo(Principal principle){
            return principle;
    }
    _/**
     *从当前请求对象中获取
     */
    _@GetMapping("/getLoginUserInfo1")
    public Authentication getLoginUserInfo1(Authentication authentication){
        return authentication;
    }
    _/**
     * 从安全应用上下文(SecurityContextHolder)获取安全应用上下文(SecurityContext),从安全应用上下文中获取认证信息
     * **@return
     ***/
    _@GetMapping("/getLoginUserInfo2")
    public Authentication getLoginUserInfo(){
        Authentication authentication = SecurityContextHolder._getContext_().getAuthentication();
        return authentication;
    }

}  

注意Authentication接口继承自 Principal
重启程序,访问

http://localhost:8080/getLoginUserInfo
http://localhost:8080/getLoginUserInfo1
http://localhost:8080/getLoginUserInfo2

运行结果

{
“authorities”: [{
“authority”: “ROLE_teacher”
}],
“details”: {
“remoteAddress”: “0:0:0:0:0:0:0:1”,
“sessionId”: “34E452050095348E6306CF95B2025CD9”
},
“authenticated”: true,
“principal”: {
“password”: null,
“username”: “thomas”,
“authorities”: [{
“authority”: “ROLE_teacher”
}],
“accountNonExpired”: true,
“accountNonLocked”: true,
“credentialsNonExpired”: true,
“enabled”: true
},
“credentials”: null,
“name”: “thomas” }

Principal 定义认证的而用户,如果用户使用用户名和密码方式登录,principal通常就是一个UserDetails(后面再说)
Credentials:登录凭证,一般就是指密码。当用户登录成功之后,登录凭证会被自动擦除,以防泄露。
authorities:用户被授予的权限信息。

7.2 配置用户权限

配置用户权限有两种方式:
配置roles
配置authorities
注意事项:
如果给一个用户同时配置roles和authorities,哪个写在后面哪个起作用
配置roles时,权限名会加上ROLE_。
修改WebSecurityConfig代码中的

    // 注意 1 哪个写在后面哪个起作用 2 角色变成权限后会加一个ROLE_前缀,比如ROLE_teacher
        //        UserDetails user2 = User.builder()

//                .username("thomas")

//                .password(passwordEncoder().encode("123456"))

//                .authorities("teacher:add","teacher:update")

//                .roles("teacher")

//                .build();
        UserDetails user2 = User._builder_()
                .username("thomas")             .password(passwordEncoder().encode("123456"))
                .roles("teacher")
                .authorities("teacher:add","teacher:update")
                .build();  

重启程序使用thomas登录,然后查看用户认证信息

http://localhost:8080/getLoginUserInfo

可以看到authorities的情况。
从设计层面讲,角色和权限是两个完全不同的东西
从代码层面来讲,角色和权限并没有太大区别,特别是在Spring Security中

8 授权(对URL进行授权)

上面讲的实现了认证功能,但是受保护的资源是默认的,默认所有认证(登录)用户均可以访问所有资源,不能根据实际情况进行角色管理,要实现授权功能,需重写WebSecurityConfigureAdapter 中的一个configure方法
复制springsecurity-06-loginuser-info 工程,然后改名为springsecurity-07-url
新建WebSecurityConfig类,重写configure(HttpSecurity http)方法
WebSecurityConfig 完整代码如下:

@Configuration

@Slf4j

  public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //角色student或者teacher都可以访问/student/** 这样的url
                .mvcMatchers("/student/*").hasAnyRole("student", "teacher")
                // 角色teacher 可以访问teacher/**
                .mvcMatchers("/teacher/**").hasRole("teacher")
                //权限admin:query 可以访问/admin**

//                .mvcMatchers("/admin/**").hasAuthority("admin:query")
                //角色teacher 或者权限admin:query 觉可以访问admin/**
                .mvcMatchers("/admin/**").access("hasRole('teacher') or hasAuthority('admin:query')")
                //任何请求均需要认证
                .anyRequest().authenticated();
        //使用表单登录
        http.formLogin();
    }
  
} 

使用admin登录,访问

http://localhost:8080/teacher/query
http://localhost:8080/student/query
http://localhost:8080/admin/query

分别查看效果,实现权限控制
上面是对URL资源进行控制,就是哪些权限可以访问哪些URL。

9 授权(方法级别的权限控制)

上面学习的认证与授权都是基于URL的,我们也可以通过注解灵活的配置方法安全,我们先通过@EnableGlobalMethodSecurity开启基于注解的安全配置。

9.1 新建模块springsecurity-08-method

9.2 添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency> 

9.3 新建启动类并复制 CurrentLoginUserInfoController类

新建启动类Application,学员自行创建

9.4 新建service及其实现

com.powernode.service 新建教师接口

public interface TeacherService {
    String add();
    String update();
    String delete();
    String query();
  } 

com.powernode.service.impl 实现接口

@Service

@Slf4j

  public class TeacherServiceImpl implements TeacherService {
    @Override
    public String add() {
        _log_.info("添加教师成功");
        return "添加教师成功";
    }
    @Override
    public String update() {
        _log_.info("修改教师成功");
        return "修改教师成功";
    }
    @Override
    public String delete() {
        _log_.info("删除教师成功");
        return "删除教师成功";
    }
    @Override
    public String query() {
        _log_.info("查询教师成功");
        return "查询教师成功";
    }
} 

9.5 修建TeacherController

@RestController

@RequestMapping("/teacher")

  public class TeacherController {
    @Resource
    private TeacherService teacherService;
    @GetMapping("/query")
    public String queryInfo() {
        return teacherService.query();
    }
    @GetMapping("/add")
    public String addInfo() {
        return teacherService.add();
    }
    @GetMapping("/update")
    public String updateInfo() {
        return teacherService.update();
    }
    @GetMapping("/delete")
    public String deleteInfo() {
        return teacherService.delete();
    }
} 

9.6 新建安全配置类

com.powernode.config下新建用户配置类

@Configuration

  public class MySecurityUserConfig {

    @Bean

    public UserDetailsService userDetailService() {

  //        使用org.springframework.security.core.userdetails.User类来定义用户
        //定义两个用户
        UserDetails user1 = User._builder_()
                .username("eric")
                .password(passwordEncoder().encode("123456"))
                .roles("student")
                .build();
        UserDetails user2 = User._builder_()
                .username("thomas")
              .password(passwordEncoder().encode("123456"))
                .roles("teacher")
                .build();
        UserDetails user3 = User._builder_()
                .username("admin")
                .password(passwordEncoder().encode("123456"))
                .authorities("teacher:add", "teacher:update")
                .roles("teacher")
                .build();
        //创建两个用户
        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
        userDetailsManager.createUser(user1);
        userDetailsManager.createUser(user2);
        return userDetailsManager;
    }
    /*

     * 从 Spring5 开始,强制要求密码要加密
     * @return

     */

    @Bean

    public PasswordEncoder passwordEncoder(){

        //使用加密算法对密码进行加密
        return new BCryptPasswordEncoder();

    }

} 

新建WebSecurityConfig类

@Configuration
  public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override

    protected void configure(HttpSecurity http) throws Exception {//任何访问均需要认证
        http.authorizeRequests().anyRequest().authenticated();

        http.formLogin(); //使用表单登陆方式
    }

} 

9.7 启动程序并访问

访问以下地址

| http://localhost:8080/teacher/add
http://localhost:8080/teacher/update
http://localhost:8080/teacher/delete

http://localhost:8080/teacher/query

通过admin或thomas登录均可以访问所有资源

9.8 修改安全配置类WebSecurityConfig

加上启用全局方法安全注解

@EnableGlobalMethodSecurity(prePostEnabled = true)

修改后,完整代码如下:

@EnableGlobalMethodSecurity(prePostEnabled = true)

  public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override

    protected void configure(HttpSecurity http) throws Exception {

        //任何访问均需要认证
        http.authorizeRequests().anyRequest().authenticated();

        //使用表单登录
        http.formLogin();

    }

} 

9.9 修改TeacherServiceImpl

在每个方法上加上前置授权注解:@PreAuthorize
完整代码如下:

@Service

@Slf4j

  public class TeacherServiceImpl implements TeacherService {

    @Override

    @PreAuthorize("hasAuthority('teacher:add') OR hasRole('teacher')")

    public String add() {

        _log_.info("添加教师成功");

        return "添加教师成功";

    }

  

    @Override

    @PreAuthorize("hasAuthority('teacher:update')")

    public String update() {

        _log_.info("修改教师成功");

        return "修改教师成功";

    }

  

    @Override

    @PreAuthorize("hasAuthority('teacher:delete')")

    public String delete() {

        _log_.info("删除教师成功");

        return "删除教师成功";

    }

  

    @Override

    @PreAuthorize("hasRole('teacher')")

    public String query() {

        _log_.info("查询教师成功");

        return "查询教师成功";

    }

} 

9.10 启动并运行

运行程序分别使用admin和teacher登录,可以查看不同效果
http://localhost:8080/teacher/add
http://localhost:8080/teacher/update
http://localhost:8080/teacher/delete
http://localhost:8080/teacher/query

发现thomas可以访问添加和查询,别的不能访问,amdin可以访问添加和更新,别的不能访问。
代码说明:
Ø EnableGlobalMethodSecurity注解的属性prePostEnabled = true 解锁@PreAuthorize 和@PostAuthorize注解,@PreAuthorize 在方法执行前进行验证,@PostAuthorize 在方法执行后进行验证
Ø EnableGlobalMethodSecurity的securedEnabled = true 解锁@Secured注解,@Secured和@PreAuthorize用法基本一样 @Secured对应的角色必须要有ROLE_前缀

10 SpringSecurity 返回json

前后端分离成为企业应用开发中的主流,前后端分离通过json进行交互,登录成功和失败后不用页面跳转,而是一段json提示

10.1 新建模块springsecurity-09-json

10.2 添加依赖

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-security</artifactId>

</dependency> 

10.3 新建三个controller和获取登录用户信息的controller

参照1.2.4 创建,可以直接拷贝过来

10.4 新建启动类

com.powernode下新建Application类,学员自行创建

10.5 创建统一响应类HttpResult

在com.powernode.vo中创建该类

@Data

@AllArgsConstructor

@NoArgsConstructor@Builder
  public class HttpResult {

    private Integer code;

    private String msg;

    private Object data;

    public HttpResult(Integer code, String msg) {

        this.code = code;

        this.msg = msg;

    }

} 

10.6 创建登录成功处理器

com.powernode.config 包下创建

@Component

  public class MyAutheticationSuccessHandle implements AuthenticationSuccessHandler {@Resource
    private ObjectMapper objectMapper;

  

    @Override

    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        response.setCharacterEncoding("UTF-8");

        response.setContentType("application/json;charset=utf-8");

        HttpResult httpResult = new HttpResult(200, "登录成功", authentication);

        String str = objectMapper.writeValueAsString(httpResult);

        response.getWriter().write(str);

        response.getWriter().flush();

    }

} 

10.7 创建登录失败处理器

 _/**

 * 登陆失败的处理器
 */

  _@Component@Slf4jpublic class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

  

    @Resource
    private ObjectMapper objectMapper;

  

  

    _/**

     * **@param **request 当前的请求对象
     * **@param **response 当前的响应对象
     * **@param **exception 失败的原因的异常
     * **@throws **IOException

     * **@throws **ServletException

     */

    _@Override

    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {

        System._err_.println("登陆失败");

        //设置响应编码
        response.setCharacterEncoding("UTF-8");

        response.setContentType("application/json;charset=utf-8");

        //返回JSON出去
        HttpResult result=HttpResult.builder()                .code(-1)                .msg("登录失败")                .build();                if(exception instanceof BadCredentialsException){

            result.setData("密码不正确");

        }else if(exception instanceof DisabledException){

            result.setData("账号被禁用");

        }else if(exception instanceof UsernameNotFoundException){

            result.setData("用户名不存在");

        }else if(exception instanceof CredentialsExpiredException){

            result.setData("密码已过期");

        }else if(exception instanceof AccountExpiredException){

            result.setData("账号已过期");

        }else if(exception instanceof LockedException){

            result.setData("账号被锁定");

        }else{

            result.setData("未知异常");

        }

        //把result转成JSON

        String json = objectMapper.writeValueAsString(result);

        //响应出去
        PrintWriter out = response.getWriter();

        out.write(json);

        out.flush();

    }

} 

10.8 创键无权限处理器

_/**

 * 无权限的处理器
 */

  _@Component

  public class MyAccessDeniedHandler implements AccessDeniedHandler {

  

    //声明一个把对象转成JSON的对象@Resource
    private ObjectMapper objectMapper;

  

    @Override

    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {

        //设置响应编码
        response.setCharacterEncoding("UTF-8");

        response.setContentType("application/json;charset=utf-8");

        //创建响应对象
        HttpResult result= HttpResult.builder()

             .code(-1)

             .msg("用户没有访问权限")

             .build();        //把result转成JSON

        String json = objectMapper.writeValueAsString(result);

        //响应json出去
        PrintWriter out = response.getWriter();

        out.write(json);

        out.flush();

    }

} 

10.9 创建登出(退出)处理器

 _/**

 * 退出成功的处理器
 */

  _@Component

  public class MyLogoutSuccessHandler implements LogoutSuccessHandler {

  

    //声明一个把对象转成JSON的对象@Resource
    private ObjectMapper objectMapper;

  

    _/**

     *

     * **@param **request

     * **@param **response

     * **@param **authentication 当前退出的用户对象
     * **@throws **IOException

     * **@throws **ServletException

     */

    _@Override

    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        System._out_.println("退出成功");

        //设置响应编码
        response.setCharacterEncoding("UTF-8");

        response.setContentType("application/json;charset=utf-8");

        //返回JSON出去
                HttpResult result= HttpResult.builder()                .code(200)                .msg("退出成功")                .build();
        //把result转成JSON

        String json = objectMapper.writeValueAsString(result);

        //响应出去
        PrintWriter out = response.getWriter();

        out.write(json);

        out.flush();

    }

} 

10.10 创建用户配置类

@Configuration

  public class MySecurityUserConfig {

    @Bean

    public UserDetailsService userDetailService() {

  //        使用org.springframework.security.core.userdetails.User类来定义用户
        //定义用户
        UserDetails user1 = User._builder_()

                .username("eric")

                .password(passwordEncoder().encode("123456"))

                .roles("student")

                .build();

        UserDetails user2 = User._builder_()

                .username("thomas")

                .password(passwordEncoder().encode("123456"))

                .roles("teacher")

                .build();

        UserDetails user3 = User._builder_()

                .username("admin")

                .password(passwordEncoder().encode("123456"))

                .roles("admin")

                .build();

        //创建用户
        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();

        userDetailsManager.createUser(user1);

        userDetailsManager.createUser(user2);

        userDetailsManager.createUser(user3);

        return userDetailsManager;

    }

  

    /*

     * 从 Spring5 开始,强制要求密码要加密
     * @return

     */

    @Bean

    public PasswordEncoder passwordEncoder(){

        //使用加密算法对密码进行加密
        return new BCryptPasswordEncoder();

    }

  

} 

10.11 安全配置类WebSecurityConfig


```java
@Configuration

  public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  

    // 注入登陆成功的处理器
    @Autowired

    private MyAutheticationSuccessHandle successHandler;

  

    // 注入登陆失败的处理器
    @Autowired

    private MyAuthenticationFailureHandler failureHandler;

  

    // 注入没有权限的处理器
    @Autowired

    private MyAccessDeniedHandler accessDeniedHandler;

  

    //  注入退出成功的处理器
    @Autowired

    private MyLogoutSuccessHandler logoutSuccessHandler;

  

    @Override

    protected void configure(HttpSecurity http) throws Exception {

  //配置拒绝访问处理器        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);

 //配置登录成功处理器,配置登录失败处理器       http.formLogin().successHandler(successHandler).failureHandler(failureHandler); //配置退出成功处理器
        http.logout().logoutSuccessHandler(logoutSuccessHandler);

        http.authorizeRequests().mvcMatchers("/teacher/**").hasRole("teacher").anyRequest().authenticated();

    }

} 

10.12 启动程序

10.13 访问测试

可以使用admin用户实验登录失败、登录成功、退出和访问http://localhost:8080/teacher/query 查看无权访问的效果

11 使用自定义UserDetailsService实现获取用户认证信息

11.1 新建子模块springsecurity-10-userdetailservice

11.2 添加依赖

 <dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-security</artifactId>

</dependency> 

11.3 新建启动类

com.powernode包下新建启动类Application,学员自行创建

11.4 新建三个controller

参照1.2.4 创建,可以直接拷贝过来

11.5 新建获取登录用户认证信息的controller

拷贝7.1 即可

11.6 新建用户信息类

com.powernode.vo包下新建SecurityUser 类,该类实现接口UserDetails接口

public class SecurityUser implements UserDetails {

    @Override

    public Collection<? extends GrantedAuthority> getAuthorities() {

        return null;

    }

  

    @Override

    public String getPassword() {

        //用户密码使用密文        return new BCryptPasswordEncoder().encode("123456");
    }

  

    @Override

    public String getUsername() {//定义用户名
        return "thomas";

    }

  

    @Override

    public boolean isAccountNonExpired() {//账号是否未过期,返回true 未过期
        return true;

    }

  

    @Override

    public boolean isAccountNonLocked() {//账号是否未锁定,返回true 未锁定
        return true;

    }

  

    @Override

    public boolean isCredentialsNonExpired() {//凭据(凭证),目前可以理解成密码,是否未过期,返回true 未过期
        return true;

    }

  

    @Override

    public boolean isEnabled() {//账号是否可以,返回true可用
        return true;

    }

} 

代码说明:
用户实体类需要实现UserDetails接口,并实现该接口中的7个方法, UserDetails 接口的7个方法如下图:

11.7 新建类实现UserDetailService接口

com.powernode.service.impl 包下新建UserServiceImpl 实现UserDetailService

@Service

  public class UserServiceImpl implements UserDetailsService {

    @Override

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        SecurityUser securityUser= new SecurityUser();

        if(username==null &#124;&#124; !username.equals(securityUser.getUsername())){

            throw new UsernameNotFoundException("该用户不存在");

        }

        return securityUser;

    }

} 

11.8 新建安全配置类

com.powernode.config下新建WebSecurityConfig类,配置密码编码器

@Configuration

@Slf4j

  public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean

    public PasswordEncoder passwordEncoder(){

        return new BCryptPasswordEncoder();

    }

}

启动程序,并使用浏览器访问程序:http://localhost:8080/student/query
发现需要登录,使用thomas/123456 登录后,即可正常访问。
访问:http://localhost:8080/getLoginUserInfo
发现该用户并没有权限信息

11.9 配置用户权限信息

修改SecurityUser类中的getAuthorities 方法

@Override

    public Collection<? extends GrantedAuthority> getAuthorities() {

       GrantedAuthority g1=()->"student:query"; //使用lambda表达式创建接口实现类,而不是使用匿名内部类来实现接口
  //       GrantedAuthority g1=new SimpleGrantedAuthority("student:query"); // 使用子类创建对象
       List<GrantedAuthority> grantedAuthorityList=new ArrayList<>();

       grantedAuthorityList.add(g1);

       return grantedAuthorityList;

    } 

11.10 修改要访问controller中的方法需要哪些权限

修改WebSecurityConfig,添加全局方法拦截注解@EnableGlobalMethodSecurity(prePostEnabled = true)
注意可以去掉:@Configuration注解了

修改StudentController
添加 @PreAuthorize(“hasAuthority(‘student:query’)”) 注解修改后如下:

@RestController

@RequestMapping("/student")

  public class StudentController {

    @GetMapping("/query")

    @PreAuthorize("hasAuthority('student:query')")

    public String queryInfo(HttpServletRequest request){

        return "I am a student,My name is Eric";

    }

} 

修改TeacherController
添加 @PreAuthorize(“hasAuthority(teacher:query’)”) 注解修改后如下:

@RestController

@RequestMapping("/teacher")

  public class TeacherController {

    @GetMapping("/query")

    @PreAuthorize("hasAuthority('teacher:query')")

    public String queryInfo(){

        return "I am a teacher,My name is Thomas";

    }

} 

启动测试,使用thomas/123456 登录系统,发现可以访问student/query,不可以访问teacher/query
再次访问:http://localhost:8080/getLoginUserInfo 查看用户信息

11.11 为什么讲这个示例?

是为了使用数据库存储用户角色权限信息做准备,只要从数据库中取出数据存储到实现UserDetails 的接口的类中即可,比如SecurityUser 中即可。

12 基于数据库的认证

12.1 创建数据库security_study和表

创建数据库security_study
导入数据库脚本security_study.sql

12.2 创建模块springsecurity-11-database-authentication

12.3 添加依赖

<parent>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-parent</artifactId>

    <version>2.6.13</version>

</parent>

  

<properties>

    <maven.compiler.source>8</maven.compiler.source>

    <maven.compiler.target>8</maven.compiler.target>

    <java.version>8</java.version>

</properties>

  

<dependencies>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-web</artifactId>

    </dependency>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-test</artifactId>

        <scope>test</scope>

    </dependency>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-security</artifactId>

    </dependency>

    <dependency>

        <groupId>mysql</groupId>

        <artifactId>mysql-connector-java</artifactId>

        <scope>runtime</scope>

    </dependency>

    <dependency>

        <groupId>org.mybatis.spring.boot</groupId>

        <artifactId>mybatis-spring-boot-starter</artifactId>

        <version>2.2.2</version>

    </dependency>

    <dependency>

        <groupId>org.projectlombok</groupId>

        <artifactId>lombok</artifactId>

        <optional>true</optional>

    </dependency>

</dependencies>

<build>

    <plugins>

        <plugin>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-maven-plugin</artifactId>

        </plugin>

    </plugins>

</build> 

12.4 配置数据源和mybatis

新建配置文件application.yml并配置数据源和mybatis

spring:

  datasource:

    driver-class-name: com.mysql.cj.jdbc.Driver

    url: jdbc:mysql://127.0.0.1:3306/security_study?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai

    username: root

    password: root

  mybatis:

  type-aliases-package: com.powernode.entity

  configuration:

    map-underscore-to-camel-case: true

    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

  mapper-locations: classpath:mapper/*.xml 

12.5 新建各个包

12.6 新建用户实体类

com.powernode.entity 包下新建用户实体类

@Data@AllArgsConstructor@NoArgsConstructor@Builder

  public class SysUser implements Serializable {

    private Integer userId;

    private String username;

    private String password;

    private String sex;

    private String address;

    private Integer enabled;

    private Integer accountNoExpired;

    private Integer credentialsNoExpired;

    private Integer accountNoLocked;

  } 

12.7 新建用户mapper和映射文件

com.powernode.dao下新建

public interface SysUserDao {

    _/**

     * 根据用户名获取用户信息
     * **@param **username

     * **@return

     ***/

    _SysUser getByUserName(@Param("username") String username);

  }

mapper下新建映射文件SysUserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper

        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.powernode.dao.SysUserDao">

    <select id="getByUserName" resultType="sysUser">

        select user_id,username,password,sex,address,enabled,account_no_expired,credentials_no_expired,account_no_locked

        from sys_user where username=#{username}

    </select>

</mapper> 

注意select后面不要使用*。

12.8 新建启动类

com.powernode包下新建启动类

@SpringBootApplication

@MapperScan("com.powernode.dao")

  public class Application {

    public static void main(String[] args) {

        SpringApplication._run_(Application.class,args);

    }

} 

12.9 单元测试

测试dao

@SpringBootTest

  class SysUserDaoTest {

    @Resource

    private SysUserDao sysUserDao;

    @Test

    void getByUserName() {

        SysUser sysUser = sysUserDao.getByUserName("obama");

        _assertNotNull_(sysUser);

    }

} 

注意单元测试要测试哪些:dao–service-controller,实体类一般不需要测试

12.10 新建安全用户类

com.powernode.vo包下新建类

public class SecurityUser implements UserDetails {

    private  final SysUser sysUser;

  

    public SecurityUser(SysUser sysUser) {

        this.sysUser=sysUser;

    }

  

    @Override

    public Collection<? extends GrantedAuthority> getAuthorities() {

        return null;

    }

  

    @Override

    public String getPassword() {

        String userPassword=this.sysUser.getPassword();

//注意清除密码
this.sysUser.setPassword(null);

return userPassword;
    }

  

    @Override

    public String getUsername() {

        return sysUser.getUsername();

    }

  

    @Override

    public boolean isAccountNonExpired() {

        return sysUser.getAccountNoExpired().equals(1);

    }

  

    @Override

    public boolean isAccountNonLocked() {

        return sysUser.getAccountNoLocked().equals(1);

    }

  

    @Override

    public boolean isCredentialsNonExpired() {

        return sysUser.getCredentialsNoExpired().equals(1);

    }

  

    @Override

    public boolean isEnabled() {

        return sysUser.getEnabled().equals(1);

    }

} 

12.11新建UserServiceImpl 实现UserDetailService接口

@Service

  public class UserServiceImpl implements UserDetailsService {

    @Resource

    private SysUserDao sysUserDao;

  

    @Override

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        SysUser sysUser = sysUserDao.getByUserName(username);

        if(null==sysUser){

            throw new UsernameNotFoundException("账号不存在");

        }

        return new SecurityUser(sysUser);

    }

} 

12.12 新建service单元测试

@SpringBootTest

  class UserServiceImplTest {

    @Resource

    private UserServiceImpl userService;

    @Test

    void loadUserByUsername() {

        UserDetails userDetails = userService.loadUserByUsername("obama");

        _assertNotNull_(userDetails);

    }

} 

12.13 新建两个控制器

@RestController

@Slf4j

@RequestMapping("/student")

  public class StudentController {

    @GetMapping("/query")

    public String queryInfo(){

        return "query student";

    }

    @GetMapping("/add")

    public String addInfo(){

        return "add  student!";

    }

    @GetMapping("/update")

    public String updateInfo(){

        return "update student";

    }

    @GetMapping("/delete")

    public String deleteInfo(){

        return "delete  student!";

    }

    @GetMapping("/export")

    public String exportInfo(){

        return "export  student!";

    }

} 
@RestController

@Slf4j

@RequestMapping("/teacher")

  public class TeacherController {

    @GetMapping("/query")

    @PreAuthorize("hasAuthority('teacher:query')")

    public String queryInfo(){

        return "I am a teacher!";

    }

} 

12.14 新建获取登录用户认证信息的controller

从7.1 中拷贝即可

12.15 新建web安全配置类

@EnableGlobalMethodSecurity(prePostEnabled = true)

  @Slf4j

  public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  

    @Bean

    public PasswordEncoder passwordEncoder() {

        return new BCryptPasswordEncoder();

    }

  

    @Override

    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests().anyRequest().authenticated();

        http.formLogin();

    }

} 

启动并进行各种测试
使用thomas和obama分别登录测试,发现student/query等能访问(不需要访问权限),teacher/query 不能访问(需要访问权限),原因

http://localhost:8080/getLoginUserInfo


发现用户没有权限,但是/teacher/query 需要访问权限,所以/teacher/query 无法访问

13 基于数据库的方法授权

13.1 新建模块

复制springsecurity-11-database-authentication 改名为springsecurity-12-database-authorization-method
注意这个工程已经有认证功能了。下面咱们看下如何设置用户的权限

13.2 新建菜单(权限)实体类

@Data@AllArgsConstructor@NoArgsConstructor@Builder

  public class SysMenu implements Serializable {

    private Integer id;

    private Integer pid;

    private Integer type;

    private String name;

    private String code;

  }

13.3 新建权限mapper和映射文件

public interface SysMenuDao {

   List<String> queryPermissionByUserId(@Param("userId") Integer userId);

  }

映射文件SysMenuMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper

        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.powernode.dao.SysMenuDao">

    <select id="queryPermissionByUserId" resultType="string">

        SELECT distinct sm.`code` FROM `sys_role_user` sru inner join sys_role_menu srm

  

on sru.rid=srm.rid inner join sys_menu sm on srm.mid=sm.id  where sru.uid=#{userId}  and sm.delete_flag=0
    </select>

</mapper> 

13.4 权限dao的单元测试

@SpringBootTest

  class SysMenuDaoTest {

    @Resource

    private SysMenuDao sysMenuDao;

    @Test

    void queryPermissionByUserId() {

        List<String> menuList = sysMenuDao.queryPermissionByUserId(1);

        _assertTrue_(!menuList.isEmpty());

    }

} 

13.5 修改SecurityUser实体类

加入一个属性

private List simpleGrantedAuthorities;

修改方法getAuthorities

@Override

  public Collection<? extends GrantedAuthority> getAuthorities() {

    return simpleGrantedAuthorities;

  } 

添加一个set方法

public void setSimpleGrantedAuthorities(List<SimpleGrantedAuthority> simpleGrantedAuthorities) {

    this.simpleGrantedAuthorities = simpleGrantedAuthorities;

  } 

13.6 修改UserServiceImpl

增加设置权限的步骤,修改后如下:

@Service

@Slf4j

  public class UserServiceImpl implements UserDetailsService {

    @Resource

    private SysUserDao sysUserDao;

    @Resource

    private SysMenuDao sysMenuDao;

  

    @Override

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        SysUser sysUser = sysUserDao.getByUserName(username);

        if(null==sysUser){

            throw new UsernameNotFoundException("账号不存在");

        }

        List<String> strList=sysMenuDao.queryPermissionByUserId(sysUser.getUserId());//使用stream流来转换// SimpleGrantedAuthority::new 相当于调用构造方法
        List<SimpleGrantedAuthority> grantedAuthorities=strList.stream().map(SimpleGrantedAuthority::new).collect(_toList_());

        SecurityUser securityUser = new SecurityUser(sysUser);

        securityUser.setSimpleGrantedAuthorities(grantedAuthorities);

        return securityUser;

    }

} 

启动并进行各种测试
使用thomas和obama分别登录测试,发现已经有权限功能了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/455583.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

react-9 函数式写法rsc,配合HOOKS基础钩子函数

函数组件和类组件区别&#xff1a; 1.函数组件中没有this 2.函数组件无需继承 3.函数组件默认没有状态&#xff0c;想要使用组件状态必须通过 HOOK 函数引入 4.函数组件默认没有生命周期函数 5.函数组件的渲染&#xff0c;只需要一次函数调用即可 useState &#xff1a;用…

【react全家桶学习】react中JSX语法规则

目录 JSX的定义 XML的含义 JSX的语法规则 &#xff08;1&#xff09;定义类名&#xff0c;用className而不是class &#xff08;2&#xff09;如何使用内联样式设置样式 &#xff08;3&#xff09;虚拟dom必须只有一个根标签 &#xff08;4&#xff09;在react组件中使用变…

谷歌将发布全新搜索引擎,你期待吗?

Google一身自带AI属性的新搜索最首要的目标并非急于取代传统搜索引擎&#xff0c;或者说彻底打败ChatGPT&#xff0c;而是能够用全新的产品说服用户&#xff0c;变得与竞争对手同样“强大、能力出众以及顺应AI潮流”。 对于Google而言&#xff0c;搜索就是命脉。Google每年的收…

Java基础——多线程创建

&#xff08;1&#xff09;什么是线程&#xff1f; 线程(thread)是一个程序内部的一条执行路径。程序中只有一条执行路径&#xff0c;那么这个程序就是单线程的程序。 &#xff08;2&#xff09;多线程是什么&#xff1f; 多线程是指从软硬件上实现多执行流程的技术。 &…

Python入门教程+项目实战-10.5节: 程序实战-冒泡排序算法

目录 10.5.1 排序算法简介 10.5.2 冒泡排序算法 10.5.3 系统学习python 10.5.1 排序算法简介 所谓排序&#xff0c;是指将数据集合中的元素按从小到大的顺序进行排列&#xff0c;或按从大到小的顺序进行排列。前者称为升序排序&#xff0c;后者称为降序排序。在数据结构与算…

jenkins自动化部署配置

文章目录 1. jenkins 插件安装2. 配置2.1 全局工具配置2.2 全局配置2.2.1 gitee 配置 3. 创建任务添加gitee ssh jenkins 开机自启动 1. jenkins 插件安装 ant Build Failure AnalyzerBuild Monitor ViewBuild Timeout dockerEmail Extension Plugin giteegithubgradle javama…

【11 EL表达式JSTL 学习笔记 】

EL表达式 学习笔记 1. EL表达式介绍2. EL表达式的基本使用3. EL 表达式获取数据4. EL注意事项5. EL表达式运算符关系运算符逻辑运算符empty&三元运算符 6. EL表达式细节7. EL表达式11个隐式对象8. JSTL8.1 JSTL的基本使用 1. EL表达式介绍 简化了 java代码块和jsp表达式的…

ChatGPT的开源平替,终于来了!

最近这段时间&#xff0c;一个号称全球最大ChatGPT开源平替项目Open Assistant引起了大家的注意。 这不最近还登上了GitHub的Trending热榜。 https://github.com/LAION-AI/Open-Assistant 根据官方的介绍&#xff0c;Open Assistant也是一个对话式的大型语言模型项目&#xff…

RUST 每日一省:生命周期作用域

生命周期 一个变量的生命周期就是它从创建到销毁的整个过程。 作用域 我们声明的每个变量都有作用域。作用域其实是变量和值存在的环境。作用域是由一对花括号表示的。例如&#xff0c;使用块表达式会创建一个作用域&#xff0c;即任何以花括号开头和结尾的表达式。此…

RabbitMQ-整合mqtt

用 springboot rabbitmq可以搭建物联网&#xff08;IOT&#xff09;平台&#xff0c;rabbitmq 不是消息队列吗&#xff0c;原来rabbitmq有两种协议&#xff0c;消息队列是用的AMQP协议&#xff0c;而用在智能硬件中的是MQTT协议。 一、rabbitmq是什么&#xff1f; RabbitMQ就…

一张图了解GPU、CUDA、CUDA toolkit和pytorch的关系

文章目录 GPU、Cuda Driver和 Cuda Toolkit的图解关系省流&#xff0c;简略版本要实现多版本的cuda怎么办 复杂版&#xff08;你要是觉得简略版说的太简单&#xff0c;这里给你找文档证明&#xff09;一、Nvidia Driver和CUDA Toolkit的关系安装GPU显卡驱动Nvidia Driver 二、C…

uniapp请求图片时候发现提示GET http://localhost:xxxx/undefined 401,undefined:1解决办法【伸手党福利】

同理解决问题&#xff1a;所有请求发起完成之后执行业务逻辑 目录 现象原因解决办法方法1&#xff1a;提前给变量一个非空默认值方法2&#xff1a;使用前端图片代替后端方法3&#xff1a;使用异步加载判断&#xff1a;注意&#xff1a;这种直接在页面判断内容是否为空或者undef…

Java JVM基础入门(一):jvm的组成、串池、常量池、常用程序调优参数

JVM JVM是java的虚拟机&#xff0c;java的运行环境&#xff08;java二进制字节码的运行环境&#xff09; 好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收功能 JDK、JRE、JVM的关系图 常见的JVM&#xff1a; oracle的Hotspot是我们通常使…

矿山电子封条智能监管算法 yolov8

矿山电子封条智能监管系统通过YOLOv8python网络模型技术&#xff0c;矿山电子封条智能监管算法模型在对矿井人数变化、生产作业状态、出入井人员等情况实时监测分析&#xff0c;发现煤矿人员作业及状态异常动态及时告警&#xff0c;自动将报警信息推送给后台。YOLOv8 算法的核心…

k8s部署ingress-nginx步骤

目录 一、ingress简介 二、部署ingress controller、ingress-service 三、创建对外服务deployment和service 四、创建HTTP代理yaml 五、测试 六、公网域名测试 七、参考博客 一、ingress简介 service的作用体现在两个方面&#xff0c;对集群内部&#xff0c;它不断跟踪…

[计算机图形学]光线追踪的基本原理(前瞻预习/复习回顾)

一、光栅化的弊端 我们为什么要用光线追踪呢&#xff0c;在之前的篇章中&#xff0c;我们提到了&#xff0c;光栅化的方式很难表示一些全局的效果&#xff0c;如(1)软阴影&#xff0c;(2)Glossy的反射(类似镜子但又不像镜子那么光滑的材质&#xff0c;如打磨的铜镜和一些金属)&…

【GIT】git push后github没看到pull requests解决

当你在Github上push代码后&#xff0c;如果在远程仓库中没有看到pull request请求&#xff0c;那么有以下几种可能的原因&#xff1a; 未创建pull request 如果在本地使用git push命令将修改推送到Github上&#xff0c;但还没有在Github上创建pull request请求&#xff0c;则不…

FreeRTOS 队列(一)

文章目录 一、队列简介1. 数据存储2. 多任务访问3. 出队阻塞4. 入队阻塞5. 队列操作过程图示 二、队列结构体三、队列创建1. 函数原型&#xff08;1&#xff09;函数 xQueueCreate()&#xff08;2&#xff09;函数 xQueueCreateStatic()&#xff08;3&#xff09;函数 xQueueGe…

Golang每日一练(leetDay0045)

目录 133. 克隆图 Clone Graph &#x1f31f;&#x1f31f; 134. 加油站 Gas Station &#x1f31f;&#x1f31f; 135. 分发糖果 Candy &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 …

【PR 基础】设置上下黑白边的两种方法

方法1 点击 文件-》新建-》旧版标题 点击确定 点击矩形工具 利用矩形工具框选出上下黑白边 款选完成后点击关闭 将刚创建的字幕拖入轨道 可以修改其持续时长与视频时长保持一致 如果想要修改字幕可以双击来修改 比如可以将颜色改为黑色 方法2 点击号&#xff0c;再选择安全边…