Springsecurity课程笔记06-13章基于数据库的方法授权

动力节点Springsecurity视频课程

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分别登录测试,发现已经有权限功能了

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IJKPLAYER源码分析-常用API

前言 本文简要介绍IJKPLAYER的几个常用API&#xff0c;以API使用的角度&#xff0c;来审视其内部运作原理。这里以iOS端直播API调用切入。 调用流程 init 创建播放器实例后&#xff0c;会先调用init方法进行初始化&#xff1a; - (IJKFFMediaPlayer *)init {self [super ini…

计算机网络复习题+答案

文章目录 导文题目一、单项选择题二、填空题三、判断改错题,判断下列命题正误,正确的在其题干后的括号内打“√”,错误的打“”,并改正。四、名词解释五、简答题六、应用题导文 计算机网络复习题 题目 一、单项选择题 在应用层协议中,主要用于IP地址自动配置的协议是: (…

文案自动修改软件-文案自动改写的免费软件下载

文章生成器ai写作机器人 随着人工智能技术的飞速发展&#xff0c;越来越多的新型产品被推向市场。其中&#xff0c;文章生成器AI写作机器人是一个备受关注的新兴行业。它使用机器学习和自然语言处理等技术&#xff0c;为用户自动生成高质量的文章和内容&#xff0c;帮助用户在…

Python——第2章 数据类型、运算符与内置函数

目录 1 赋值语句 2 数据类型 2.1 常用内置数据类型 2.1.1 整数、实数、复数 2.1.2 列表、元组、字典、集合 2.1.3 字符串 2.2 运算符与表达式 2.2.1 算术运算符 2.2.2 关系运算符 2.2.3 成员测试运算符 2.2.4 集合运算符 2.2.5 逻辑运算符 2.3 常用内置…

本地搭建属于自己的ChatGPT:基于PyTorch+ChatGLM-6b+Streamlit+QDrant+DuckDuckGo

本地部署chatglm及缓解时效性问题的思路&#xff1a; 模型使用chatglm-6b 4bit&#xff0c;推理使用hugging face&#xff0c;前端应用使用streamlit或者gradio。 微调对显存要求较高&#xff0c;还没试验。可以结合LoRA进行微调。 缓解时效性问题&#xff1a;通过本地数据库…

Mybatis高级映射及延迟加载

准备数据库表&#xff1a;一个班级对应多个学生。班级表&#xff1a;t_clazz&#xff1b;学生表&#xff1a;t_student 创建pojo&#xff1a;Student、Clazz // Student public class Student {private Integer sid;private String sname;//...... }// Clazz public class Cla…

Flutter PC桌面端 控制应用尺寸是否允许放大缩小

一、需求 桌面端中&#xff0c;登录、注册、找回密码页面不允许用户手动放大缩小&#xff0c;主页面允许 二、插件 window_manager 使用教程请参照这篇博客&#xff1a;Flutter桌面端开发——window_manager插件的使用 题外话&#xff1a; 之前使用的是bitsdojo_window插件…

[golang gin框架] 25.Gin 商城项目-配置清除缓存以及前台列表页面数据渲染公共数据

配置清除缓存 当进入前台首页时,会缓存对应的商品相关数据,这时,如果后台修改了商品的相关数据,缓存中的对应数据并没有随之发生改变,这时就需要需改对应的缓存数据,这里有两种方法: 方法一 在管理后台操作直接清除缓存中的所有数据,当再次访问前台首页时,就会先从数据库中获取…

记frp内网穿透配置

这两天由于想给客户看一下我们的系统&#xff0c;于是想到用内网穿透&#xff0c;但是怎么办呢&#xff0c;没有用过呀&#xff0c;于是各处找资料&#xff0c;但是搞完以后已经不记得参考了那些文档了&#xff0c;对不起各位大神&#xff0c;就只能写出过程和要被自己蠢死的错…

初识C++(二)

在初识c&#xff08;一&#xff09;当中我们已经向大家介绍了四个c和C语言不同的使用方法。接下来我们再来向大家介绍另外的一些新的c语言的使用方法。 &#x1f335;引用 简单一点来说引用就是给已存在的变量起一个别名。这个别名通常的作用和C语言当中的指针类似。我们可以通…

牛客网刷题总结

1.利用%符号获取特定位数的数字。 2.强制类型转换 &#xff08;将float转换为int &#xff09; 3.计算有关浮点型数据时&#xff0c;要注意你计算过程中所有的数据都是浮点型 4.0/3.0 ! 4/3 4.通过位操作符实现输出2的倍数&#xff08;对于位操作符不熟悉的小伙伴可以看看我…

基于Java+SpringBoot+vue实现图书借阅和销售商城一体化系统

基于JavaSpringBootvue实现图书借阅和销售商城一体化系统 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系方…

电脑系统错误怎么办?您可以看看这5个方法!

案例&#xff1a;电脑出现系统错误该如何解决&#xff1f; 【这几天长时间使用我的电脑&#xff0c;导致它的系统出现了错误。有没有小伙伴知道如何解决电脑系统出错的问题&#xff1f;求一个能快速解决的方法。】 电脑系统出现错误是使用电脑时难免会遇到的问题之一&#xf…

【C++初阶】C++入门(二):引用内联函数auto关键字范围for循环(C++11)指针空值nullptr

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C初阶 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【C初阶】…

MySQL数据库学习笔记之存储引擎

存储引擎 MySQL体系结构 连接层 最上层是一些客户端和连接服务&#xff0c;主要完成一些类似于连接处理、授权认证、以及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限。 服务层 第二层架构主要完成大多数的核心服务功能&#xff0c;如SQL接口&am…

【Linux网络】部署YUM仓库及NFS服务

部署YUM仓库及NSF服务 一、YUM仓库1.1、YUM仓库概述1.2准备安装来源1.3在软件仓库加载非官方RPM包组1.4yum与apt 二、配置yam源与制作索引表2.1配置FTP源2.2配置国内在线yum源2.3在线源与本地源同时使用2.4建立软件包索引关系表的三种方法 三、nfs共享存储服务3.1安装软件&…

OpenAI-ChatGPT最新官方接口《微调ChatGPT模型》全网最详细中英文实用指南和教程,助你零基础快速轻松掌握全新技术(四)(附源码)

微调ChatGPT模型 前言Introduction 导言What models can be fine-tuned? 哪些模型可以微调&#xff1f;Installation 安装Prepare training data 准备训练数据CLI data preparation tool CLI数据准备工具Create a fine-tuned model 创建微调模型Use a fine-tuned model 使用微…

Windows下版本控制器(SVN) - 1、开发中的实际问题+2、版本控制简介

文章目录 基础知识-Windows下版本控制器(SVN)1、开发中的实际问题2、版本控制简介2.1 版本控制[Revision control]2.2 Subversion2.3 Subversion 的优良特性2.4 SVN 的工作原理&#xff1a;2.5 SVN 基本操作 本人其他相关文章链接 基础知识-Windows下版本控制器(SVN) 1、开发中…

docker容器:docker镜像的三种创建方法及dockerfile案例

目录 一、基于现有镜像创建 1、创建启动镜像 2、生成新镜像 二、基于本地模板创建 1、OPENVZ 下载模板 2、导入容器生成镜像 三、基于dockerfile创建 1、dockerfile结构及分层 2、联合文件系统 3、docker镜像加载原理 4、dockerfile操作常用的指令 (1)FROM指令 (…

倾斜摄影三维模型格式转换OSGB 到3Dtitles 实现的常用技术方法

倾斜摄影三维模型格式转换OSGB 到3Dtitles 实现的常用技术方法 倾斜摄影三维模型是一种用于建立真实世界三维场景的技术&#xff0c;常用于城市规划、土地管理、文化遗产保护等领域。在倾斜摄影模型中&#xff0c;OSGB格式和3Dtiles格式都是常见的数据格式。其中&#xff0c;OS…