SpringBoot Series: Integrating Spring Security

Author platform:

| CSDN:blog.csdn.net/qq_41153943

| Nuggets: juejin.cn/user/651387…

| Zhihu: www.zhihu.com/people/1024…

| GitHub: github.com/JiangXia-10…

| WeChat public account: 1024 note

This article is about 2230 words, and the expected reading time is 15 minutes

1. Introduction to Spring Security

1. Framework introduction

Spring is a very popular and successful Java application development framework. Based on the Spring framework, Spring Security provides a complete solution for web application security. Generally speaking, the security of Web applications includes two parts: user authentication (Authentication*) and user authorization (Authorization) .

(1) User authentication refers to: verifying whether a user is a legitimate subject in the system, that is to say, whether the user can access the system. User authentication generally requires the user to provide a user name and password. The system completes the authentication process by verifying the user name and password.

(2) User authorization refers to verifying whether a user has permission to perform an operation. In a system, different users have different permissions. For example, for a file, some users can only read it, while others can modify it. Generally speaking, the system assigns different roles to different users, and each role corresponds to a series of permissions.

Spring Security actually uses filter to filter the path of multiple requests.

(1) If it is based on Session, then Spring-security will analyze the sessionid in the cookie, find the session information stored in the server, and then judge whether the current user meets the requirements of the request.

(2) If it is a token, it parses out the token, and then adds the current request to the permission information managed by Spring-security

2. Authentication and authorization implementation ideas

If there are many modules in the system, each module needs to be authorized and authenticated, so we choose token-based authorization and authentication. The user successfully authenticates according to the username and password, and then obtains a series of permission values ​​of the current user role, and uses the user The name is key, the permission list is stored in the redis cache in the form of value, and the token is generated and returned according to the user name related information. The browser records the token in the cookie, and the token is carried to the header request header by default every time the api interface is called. Spring-security parses the header to obtain token information, parses the token to obtain the current user name, and obtains the permission list from redis according to the user name, so that Spring-security can determine whether the current request has permission to access

2. Integrate Spring Security

1. First create Spring Security related modules and code architecture

image.png

image.png

2. Introduce related dependencies in spring_security

<dependencies>
    <dependency>
        <groupId>com.jiangxia</groupId>
        <artifactId>common_utils</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
    <!-- Spring Security依赖 -->

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
    </dependency>
</dependencies>
复制代码

3. Introduce spring_security dependencies in modules that need to use spring_security

<dependency>
    <groupId>com.atguigu</groupId>
    <artifactId>spring_security</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
复制代码

4. Core code description

The core configuration of Spring Security is to inherit the configuration of WebSecurityConfigurerAdapter and annotate @EnableWebSecurity. This configuration specifies the processing method of username and password, opening and closing of request paths, login and logout control, and other security-related configurations. The main code is as follows:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
    private UserDetailsService userDetailsService;
    private TokenManager tokenManager;
    private DefaultPasswordEncoder defaultPasswordEncoder;
    private RedisTemplate redisTemplate;
    @Autowired
    public TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.userDetailsService = userDetailsService;
        this.defaultPasswordEncoder = defaultPasswordEncoder;
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }

    /**
     * 配置设置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling()
                .authenticationEntryPoint(new UnauthorizedEntryPoint())
                .and().csrf().disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and().logout().logoutUrl("/admin/acl/index/logout")
                .addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and().addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate)).addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();
    }
    /**
     * 密码处理
     * @param auth
     * @throws Exception
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
    }
    //配置哪些请求不拦截
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/api/**",
                "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"
               );
    }
}
复制代码

Create authentication and authorization related tool classes:

1. Password processing method

@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
    public DefaultPasswordEncoder() {
        this(-1);
    }
    /**
     * @param strength
     *            the log rounds to use, between 4 and 31
     */
    public DefaultPasswordEncoder(int strength) {
    }
    public String encode(CharSequence rawPassword) {
        return MD5.encrypt(rawPassword.toString());
    }
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
    }
}
复制代码

2. Tool class for token operation

@Component
public class TokenManager {
    private long tokenExpiration = 24*60*60*1000;
    private String tokenSignKey = "123456";
    public String createToken(String username) {
        String token = Jwts.builder().setSubject(username)
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                .signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
        return token;
    }
    public String getUserFromToken(String token) {
        String user = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
        return user;
    }
    public void removeToken(String token) {
        //jwttoken无需删除,客户端扔掉即可。
    }
}
复制代码

3. Logout and login implementation class

/**
 * 
 * 登出业务逻辑类
 *
 */
public class TokenLogoutHandler implements LogoutHandler {
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;
    public TokenLogoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }
    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        String token = request.getHeader("token");
        if (token != null) {
            tokenManager.removeToken(token);
            //清空当前用户缓存中的权限数据
            String userName = tokenManager.getUserFromToken(token);
            redisTemplate.delete(userName);
        }
        ResponseUtil.out(response, R.ok());
    }
}
复制代码

4. Unified processing method for unauthorized

public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        ResponseUtil.out(response, R.error());
    }
}
复制代码

5. Create an authentication and authorization filter

/**
 * 
 * 登录过滤器,继承UsernamePasswordAuthenticationFilter,对用户名密码进行登录校验,认证
 * 
 */
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
    private AuthenticationManager authenticationManager;
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;
    public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.authenticationManager = authenticationManager;
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
        this.setPostOnly(false);
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
    }
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException {
        try {
            User user = new ObjectMapper().readValue(req.getInputStream(), User.class);
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 登录成功
     * @param req
     * @param res
     * @param chain
     * @param auth
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
                            Authentication auth) throws IOException, ServletException {
        SecurityUser user = (SecurityUser) auth.getPrincipal();
        String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
        redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());
        ResponseUtil.out(res, R.ok().data("token", token));
    }
    /**
     * 登录失败
     * @param request
     * @param response
     * @param e
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                              AuthenticationException e) throws IOException, ServletException {
        ResponseUtil.out(response, R.error());
    }
}
复制代码
/**
 * 访问过滤器:授权filter
 * 
 */
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;
    public TokenAuthenticationFilter(AuthenticationManager authManager, TokenManager tokenManager,RedisTemplate redisTemplate) {
        super(authManager);
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }
    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        logger.info("================="+req.getRequestURI());
        if(req.getRequestURI().indexOf("admin") == -1) {
            chain.doFilter(req, res);
            return;
        }
        UsernamePasswordAuthenticationToken authentication = null;
        try {
            authentication = getAuthentication(req);
        } catch (Exception e) {
            ResponseUtil.out(res, R.error());
        }
        if (authentication != null) {
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } else {
            ResponseUtil.out(res, R.error());
        }
        chain.doFilter(req, res);
    }
    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        // token置于header里
        String token = request.getHeader("token");
        if (token != null && !"".equals(token.trim())) {
            String userName = tokenManager.getUserFromToken(token);
            List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName);
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            for(String permissionValue : permissionValueList) {
                if(StringUtils.isEmpty(permissionValue)) continue;
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
                authorities.add(authority);
            }
            if (!StringUtils.isEmpty(userName)) {
                return new UsernamePasswordAuthenticationToken(userName, token, authorities);
            }
            return null;
        }
        return null;
    }
}
复制代码

Summarize

The above is about the relevant logic of spring security in springboot. The process of spring security authorization and authentication is roughly the same as that of custom interceptors for permission interception. The authentication process is that the client user logs in, then the server caches the user login information, and finally the server returns the user information (basic information, permissions, token, etc.) to the client. In the process of authorization, firstly, the client initiates a request with a token, and the server parses the token to determine whether the user is logged in, then queries the user’s menu from the cache, determines whether the user has permission to request the menu, and finally returns the data to the client.

related suggestion

Guess you like

Origin blog.csdn.net/qq_41153943/article/details/125118857