A rookie understands the basic principles of Spring Security

Spring Security is a security component provided by Spring, which is mainly used to identify and authenticate the user's identity in the project.

The Spring security framework is Spring-Security. The function is to manage the user login of the current project and the authority management after login. It is the authority management and security solution provided by the Spring framework.

A framework with similar functions to the Spring-Security framework is Shiro

Before using Spring Security, you need to add dependencies, which can be checked directly when creating the SpringBoot project, or you can add it to the already created project:

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

After adding the dependency, when starting the service, we will see a string of randomly generated passwords, which can be used to log in to the Spring-Security system

Username: user password is the string generated when the service is started

Once this dependency is loaded, you must first log in to Spring-Security to access the resources of the current project

If we want to customize the username and password

Then you need to specify in the application.properties file

code show as below

spring.security.user.name=admin
spring.security.user.password=666666

With the above configuration, no random password will be generated when starting the service, and you can log in with the configured user name and password

In actual development, it is impossible to save the password in plain text. The plain text password can be encrypted according to a certain encryption algorithm into a cipher text password to improve security. Nowadays, popular encryption algorithms include md5, Bcrypt, etc.

Our project uses the Bcrypt encryption that comes with Spring Security, let's implement the operation of using Bcrypt to encrypt and verify the plaintext password

First, inject the encrypted object into the Spring container, create a security package in the project, and create the SecurityConfig class in the package

// @Configuration表示当前这个类也是Spring的配置类
// Spring扫描到这类时,就会把它当做配置类解析
@Configuration
public class SecurityConfig {
    //向spring容器中注入一个加密对象,用于对密码加密
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

test:

 @Autowired
    PasswordEncoder passwordEncoder;
    //加密
    @Test
    public void encode(){
        String pwd=passwordEncoder.encode("666666");
        System.out.println(pwd);
        //$2a$10$ytxmGeWcRZObqoDmlhnWxe6KqUjb9DTONmQVKkmwneHQtZw4LQtiq
    }
    //验证
    @Test
    public void decode(){
        boolean bool=passwordEncoder.matches("666666",
   "$2a$10$ytxmGeWcRZObqoDmlhnWxe6KqUjb9DTONmQVKkmwneHQtZw4LQtiq");
        System.out.println(bool);
    }

Once the project injects the PasswordEncoder type object, the SpringSecurity framework will automatically use this object to encrypt the current plaintext password, and the user's password in the application.properties file should be changed to the encrypted one.

The application.properties login configuration is modified as follows

spring.security.user.name=admin
spring.security.user.password=$2a$10$ytxmGeWcRZObqoDmlhnWxe6KqUjb9DTONmQVKkmwneHQtZw4LQtiq

When password encryption is required, the injection of the PasswordEncoder object needs to be added to the configuration class is very troublesome

We can add a specific algorithm id to the ciphertext password and SpringSecurity will automatically encrypt it according to the encryption algorithm, which is simpler

The specific method is as follows: application.properties modification

spring.security.user.name=admin
spring.security.user.password={bcrypt}$2a$10$ytxmGeWcRZObqoDmlhnWxe6KqUjb9DTONmQVKkmwneHQtZw4LQtiq

In this way, the PasswordEncoder injected in the SecurityConfig class can be annotated or deleted

@Configuration
public class SecurityConfig {

    //向spring容器中注入一个加密对象,用于对密码加密
    /*@Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }*/
}

There are many users in the project, all of which are stored in the database. It is impossible to log in by the configuration of the configuration file, so we have to learn how to verify the login in the java code. This series of verification operations should be written in a java configuration Class

Just use the SecurityConfig we created in the above chapter!!!

Code example:

@Configuration
//下面的注解表示开启Spring-Security的权限管理功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //配置登录验证(即用户名和密码的验证)
    @Override
    protected void configure(AuthenticationManagerBuilder auth)
            throws Exception {
        auth.inMemoryAuthentication().withUser("tom")
                .password("{bcrypt}$2a$10$ytxmGeWcRZObqoDmlhnWxe6KqUjb9DTONmQVKkmwneHQtZw4LQtiq")
                //上面的配置是规定了用户的用户名和密码,可以使用他们登录
                //下面的配置是规定了这个用户的特定权限
                //有了这个特定权限才可以访问特定的方法
                .authorities("/user/get");

    }
}

In the above code, we can use tom and the correct password to access general resources, but if the resources with special permissions are not accessible, we will rewrite the methods in the UserController class we wrote before.

When designing the request path, you can use {} to frame a name in the request path to represent a variable. Later, when the client submits the request, the location corresponding to the {} placeholder can be any data. Matched! When the {} placeholder is used in the request path, in the parameter list of the method to process the request, add the @PathVariable annotation before the parameter declaration to get the value of the placeholder! Put the core parameters in the URL, which is a RESTFUL style API.

@RestController
@RequestMapping("/test")
public class TestController {
 
    @Autowired
    private IUserService userService;
 
    // http://localhost:8080/test/user/1
    @GetMapping("/user/{id}")
    public User getUserById(@PathVariable("id") Integer id) {
        return userService.getById(id);
    }
 
}

If you need to restrict the access to the above URL, for example, some users can access, but some other users cannot access, you can design a "authorization string" by yourself, such as "a"or etc.! It is generally recommended to use the URL style to define access permissions, such as using or ."hello""test:user:info""/user/user/info"

@GetMapping("/user/{id}")
@PreAuthorize("hasAuthority('test:user:info')")
public User getUserById(@PathVariable("id") Integer id) {
    return userService.getById(id);
}

Note: The design of the permission string has nothing to do with the design of the URL!

An @PreAuthorizeannotation can be configured before the method of processing the request to declare that "you must have certain permissions to access the requested path", for example:

Code example:

//实际访问路径为:localhost:8080/v1/users/get?id=1
    @GetMapping("/get")
    @PreAuthorize("hasAuthority('/user/get')")//设置访问这个方法的特殊权限
    public User get(Integer id){
        User u=userService.getById(id);
        return u;
    }
    @GetMapping("/list")
    @PreAuthorize("hasAuthority('/user/list')")
    public List<User> list(){
        List<User> list=userService.list();
        return list;
    }

Regarding the above annotation configuration:

The annotation name @PreAuthorize means "verify permissions before processing the request";

The hasAuthority in the annotation attribute means "a certain authority is required";

The /user/list in the annotation attribute is a custom permission string, which is just an identification.

 User login-UserDetailsService interface Use UserDetailsService to provide authentication data

Spring Security defines an UserDetailsServiceinterface, and there are abstract methods in the interface

UserDetailsService is an interface provided by Spring Security. The method defined by this interface returns an object of type UserDetails. This object contains various information about the user, user name\password\authority, etc.

Write a class to implement this interface UserDetailsServiceImpl code is as follows:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

 The function of this method is: given a user name, you need to return the user details ( UserDetailsobject of the type). After Spring Security obtains the user details, it will automatically complete the user identity verification, including the user authority information after the verification is successful. What the framework deals with, as a developer, you only need to solve the problem of "getting user details based on user name"!

@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String name)
            throws UsernameNotFoundException {
        UserDetails user=null;
        //判断用户是不是jerry
        if("jerry".equals(name)){
            //设置jerry用户的详情
            user= User.builder()
                    .username("jerry")
                    .password("{bcrypt}$2a$10$ytxmGeWcRZObqoDmlhnWxe6KqUjb9DTONmQVKkmwneHQtZw4LQtiq")
                    .authorities("/user/get")
                    .build();
        }
        return user;
    }
}

Note: The above classes must be in the package scanned by the component, and @Componentannotated, then the Spring framework will automatically create and manage the objects of the above classes, and then you can directly assemble the objects of this class!

Then, go back to the SecurityConfigclass and apply the objects of the above class:

Then, you need to add the @EnableGlobalMethodSecurity(prePostEnabled = true) annotation before the declaration of the SecurityConfig class to allow the access permission check! E.g:

@Configuration
//下面的注解表示开启Spring-Security的权限管理功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired(required = false)
    UserDetailsServiceImpl userDetailsService;

    //配置登录验证(即用户名和密码的验证)
    @Override
    protected void configure(AuthenticationManagerBuilder auth)
            throws Exception {
        auth.userDetailsService(userDetailsService);

    }

}

Strictly speaking, the above method is not a "login" method, but a method of "getting user details". I don't even know whether the login is successful or not. Therefore, there is no password in the parameter list. In the future, Spring Security will obtain the above The object returned by the method, and verify that the password is correct, etc.

Spring-Security uses connection query to obtain user permissions

There are two methods for querying user details. One is to query user information based on the user name, and to query permissions based on the user id above, so that all the information needed for login can be obtained.

In the UserMapper interface example:

@Repository
public interface UserMapper extends BaseMapper<User> {

    // 根据用户的id查询用户的所有权限
    @Select("SELECT p.id,p.name" +
            "FROM user u" +
            "LEFT JOIN user_role ur ON u.id=ur.user_id" +
            "LEFT JOIN role r ON r.id=ur.role_id" +
            "LEFT JOIN role_permission rp ON r.id=rp.role_id" +
            "LEFT JOIN permission p ON p.id=rp.permission_id" +
            "WHERE u.id=#{id}")
    List<Permission> findUserPermissionById(Integer id);

    //按用户名查询用户
    @Select("select * from user where username=#{username}")
    User findUserByUsername(String username);
}

The basic principle of the written program is to call the Mapper written by ourselves by our own Service layer. This call is quite special. It is not the controller calling Service but Spring Security to call. We first declare a method in the IUserService interface.

public interface IUserService extends IService<User> {

    //根据用户名获得用户认证信息的业务逻辑层方法
    UserDetails getUserDetails(String username);

}

Then implement it in the implementation class UserServiceImpl:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails getUserDetails(String username) {
        //根据用户名查询出用户对象
        User user=userMapper.findUserByUsername(username);
        // 如果用户为空,表示用户名不存在,返回登录失败
        if(user==null){
            return null;
        }
        //根据用户id查询用户权限
        List<Permission> ps=userMapper
                .findUserPermissionById(user.getId());
        //将查询到的所有权限名称填装到一个数组中
        String[] auths=new String[ps.size()];
        int i=0;
        for(Permission p: ps){
            auths[i++]=p.getName();
        }
        //构建UserDetails
        UserDetails u= org.springframework.security.core
                .userdetails.User.builder()
                .username(user.getUsername())
                .password(user.getPassword())
                .authorities(auths)
                //getLocked默认都是0,所以要判==1得到false表示不锁定
                .accountLocked(user.getLocked()==1)
                //getEnabled默认都是1,所以要判==0得到false表示可用
                .disabled(user.getEnabled()==0)
                .build();
        return u;//一定不要返回null!!!!
    }
}

Finally, refactor the code example in the UserDetailsServiceImpl class:

@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private IUserService userService;
    @Override
    public UserDetails loadUserByUsername(String name)
            throws UsernameNotFoundException {
        UserDetails user=userService.getUserDetails(name);
        return user;
    }
}

User login-about access control (equivalent to interceptor)

Control the authorization scope and customize the login page authorization scope control

In fact, not all page resources of a website need to be logged in to access, so now Spring Security's restriction strategy is relatively strict. If we want to set these authorization ranges ourselves, we need the following code. Add a method code to the SecurityConfig class as follows

In SecurityConfigthe rewrite protected void configure(HttpSecurity http)method:

/设置SpringSecurity的授权范围和登录页面
  @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()   //关闭防跨域攻击
            .authorizeRequests()//开始设置授权范围
            .antMatchers(
                    "/index.html",
                    "/img/*",
                    "/js/*",
                    "/css/*",
                    "/bower_components/**",
                    "/login.html"
                    ).permitAll()     //上面路径允许直接访问
                .anyRequest().authenticated()//其他的资源需要登录
                .and().formLogin()//登录方式是表单
                .loginPage("/login.html")//指定登录的页面
                .loginProcessingUrl("/login")//当登录页面提交时,会提交给哪个路径
                .failureUrl("/login.html?error")//登录失败从新登录,显示错误提示
                .defaultSuccessUrl("/index.html")//登录成功跳转到index.html
                .and().logout() //配置登出
                .logoutUrl("/logout")//设置登出路径
                //登出成功跳转登录页面,并提示已登出
                .logoutSuccessUrl("/login.html?logout");
    }
// 准备白名单,是不需要登录就可以访问的路径
    
    // 授权设置,是相对固定的配置
    // csrf().disable() > 关闭跨域攻击
    // authorizeRequests() > 对请求进行授权
    // antMatchers() > 配置访问白名单
    // permitAll() > 对白名单中的路径进行授权
    // anyRequest() > 其它的请求
    // authenticated() > 仅经过授权的允许访问,也可以理解为“未被授权将不允许访问”
    // and.formLogin() > 未被授权的将通过登录表单进行验证登录并授权
  • httpThe configuration of the chain method of calling the parameter object is relatively fixed, you can try to understand it, or you can apply it directly;
  • The above call is antMatchers( )equivalent to configuring the whitelist when using the SpringMVC interceptor. In the method parameters, all paths that need to be directly released (locations that can be accessed without logging in) should be added, for example:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 准备白名单,是不需要登录就可以访问的路径
       // String[] antMatchers = {
        //    "/index.html"
        //};
      
        String[] antMatchers = {
    	    "/index.html",
    	    "/bower_components/**",
            "/css/**",
            "/img/**",
            "/js/**"
        };
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers(antMatchers).permitAll()
                .anyRequest().authenticated()
                .and().formLogin();
    }

    User login-replace custom login page

First, add Thymeleaf dependency to the project:

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

The customized login page will be designed as an HTML template page. When the login URL is requested, it will be forwarded to the HTML template page, and the templates folder will be created under the project’s src/main/resoueces, which is used by the SpringBoot project by default The template page folder does not need to be configured. When forwarding, the HTML template file will be queried in this folder by default. When the folder is created, drag the login.html file in the static folder to the templates folder .

Next, customize the controller and design the request path of the login page. When processing the request of the path, it is directly forwarded to the /templates/login.html file. Because Thymeleaf has already configured the prefix to /templates/ during integration, the suffix is The configuration is .html, so the view name returned in the controller is login:

Create a new controller class in the control layer to display the login.html page, named SystemController. The code is as follows

@RestController
public class SystemController {

    @GetMapping("/login.html")
    public ModelAndView loginForm(){
        return new ModelAndView("login");
    }

}
@Controller
public class SystemController {
 
    @GetMapping("/login.html")
    public String login() {
        return "login";
    }
 
    // 适用于使用@RestController时
    // public ModelAndView login() {
    //    return new ModelAndView("login");
    // }
 
}

 

 

 

 


 

Guess you like

Origin blog.csdn.net/c202003/article/details/115036902