Spring boot project integrates spring security permission authentication

1. Prepare a spring boot project

1. Introduce basic dependencies

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
		</dependency>

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.13</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.6</version>
		</dependency>

		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.3.1</version>
		</dependency>
	</dependencies>

2. Project configuration file, connect to database

server:
  port: 80
  servlet:
    context-path: /

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Shanghai
    username: root
    password: zzybzb

3. A simple interface

package com.pzz.controller;

import com.pzz.mapper.XcUserMapper;
import com.pzz.po.XcUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class LoginController {
    
    

    @Autowired
    XcUserMapper userMapper;

    @RequestMapping("/login-success")
    public String loginSuccess() {
    
    

        return "登录成功";
    }

    @RequestMapping("/r/r1")
    public String r1() {
    
    
        return "访问r1资源";
    }

    @RequestMapping("/r/r2")
    public String r2() {
    
    
        return "访问r2资源";
    }
    
}

4. The test interface can run

insert image description here

2. Integrate spring security

1. Introduce dependency configuration

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-security</artifactId>
			<version>2.1.3.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-oauth2</artifactId>
			<version>2.1.3.RELEASE</version>
		</dependency>

2. Start, you need to log in again to access

The admin here cannot log in
insert image description here

3. Add configuration file

  • User information
    Configure two users in the memory: zhangsan and lisi.
    User zhangsan has permissions of p1
    and user lisi has permissions of p2.
  • Password method:
    Temporarily adopt plain text method
  • Security interception mechanism
    Requests starting with /r/** require authentication
package com.pzz.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * @description 安全管理配置
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
    
    
        return super.authenticationManagerBean();
    }
    //配置用户信息服务
    @Bean
    public UserDetailsService userDetailsService() {
    
    
        //这里配置用户信息,这里暂时使用这种方式将用户存储在内存中
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        //在内存创建zhangsan,密码123,分配权限p1
        manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
        //在内存创建lisi,密码456,分配权限p2
        manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
        return manager;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
    
    
//        //密码为明文方式
        return NoOpPasswordEncoder.getInstance();
//        return new BCryptPasswordEncoder();
    }

    //配置安全拦截机制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http
                .authorizeRequests()
                .antMatchers("/r/**").authenticated()//访问/r开始的请求需要认证通过
                .anyRequest().permitAll()//其它请求全部放行
                .and()
                .formLogin().successForwardUrl("/login-success");//登录成功跳转到/login-success

        http.logout().logoutUrl("/logout");//退出地址

    }

}

4. Access test

  • Only requests under the /r/** path are intercepted, so login-success is no problem
    insert image description here
  • Access to /r/r1 requires logininsert image description here
  • Enter the username and password set in the configuration file to log in
    insert image description hereinsert image description here
  • Visit again, no need to log in
    insert image description here

5. Test exit

  • The configuration file configures the exit path

insert image description here

  • Log out and request /r/r1 again

insert image description here
insert image description here

6. Authorization test

User authentication is controlled by spring security when accessing system resources, to determine whether the user has access to the resource, if yes, continue to access, if not, deny access.

6.1. Configure what permissions the user has

Zhang San’s authority is p1
and Li Si’s authority is p2.
insert image description here

6.2. The relationship between specified resources and permissions

    @RequestMapping("/r/r1")
    @PreAuthorize("hasAuthority('p1')")//拥有p1权限方可访问
    public String r1() {
    
    
        return "访问r1资源";
    }


    @RequestMapping("/r/r2")
    @PreAuthorize("hasAuthority('p2')")//拥有p2权限方可访问
    public String r2() {
    
    
        return "访问r2资源";
    }

6.3. Restart and access test

  • Zhang San logged in and visited /r/r1 and /r/r2 respectively.
    insert image description here
    insert image description here
    insert image description here
    insert image description here
  • Log in with lisi and visit /r/r1 and /r/r2 respectively.
    insert image description hereinsert image description here
    insert image description here
    insert image description here

7. Working principle

7.1, working principle

The problem that pring Security solves is security access control, and the security access control function is actually to intercept all requests entering the system and verify whether each request can access the resources it expects. According to the previous knowledge learning, it can be achieved through technologies such as Filter or AOP. Spring Security's protection of Web resources is achieved by Filter, so start with this Filter and gradually deepen the principles of Spring Security.

When Spring Security is initialized, a Servlet filter named SpringSecurityFilterChain will be created, of type org.springframework.security.web.FilterChainProxy, which implements javax.servlet.Filter, so external requests will go through this class, as shown in the figure below Spring Security filter chain structure diagram:
insert image description here

FilterChainProxy is a proxy. What really works are the filters contained in SecurityFilterChain in FilterChainProxy. At the same time, these Filters are managed by Spring as beans. They are the core of Spring Security and each has its own responsibilities, but they do not directly handle user authentication. It does not directly handle user authorization, but hands them over to the authentication manager (AuthenticationManager) and the decision manager (AccessDecisionManager) for processing.

The realization of spring Security function is mainly completed by a series of filter chains.

insert image description here

The following describes the main filters and their functions in the filter chain:

  • SecurityContextPersistenceFilter
    is the entry and exit of the entire interception process (that is, the first and last interceptor). It will obtain the SecurityContext from the configured SecurityContextRepository at the beginning of the request, and then set it to the SecurityContextHolder.
    After the request is completed, save the SecurityContext held by the SecurityContextHolder to the configured SecurityContextRepository, and clear the SecurityContext held by the securityContextHolder;
  • UsernamePasswordAuthenticationFilter
    is used to handle authentication from form submissions. The form must provide the corresponding user name and password, and there are also AuthenticationSuccessHandler and AuthenticationFailureHandler for processing after successful or failed login, which can be changed according to requirements;
  • FilterSecurityInterceptor
    is used to protect web resources, using AccessDecisionManager to authorize access to the current user, which has been introduced in detail earlier;
  • ExceptionTranslationFilter
    can catch all exceptions from FilterChain and handle them. But it will only handle two types of exceptions: AuthenticationException and AccessDeniedException, and it will continue to throw other exceptions.

7.2. Execution process

The execution flow of Spring Security is as follows:
insert image description here

  1. The user's submitted username and password are obtained by the UsernamePasswordAuthenticationFilter filter in the SecurityFilterChain and encapsulated into a request for Authentication, usually the implementation class UsernamePasswordAuthenticationToken.
  2. The filter then submits the Authentication to the authentication manager (AuthenticationManager) for authentication.
  3. After successful authentication, the AuthenticationManager identity manager returns an Authentication instance filled with information (including the permission information, identity information, and details mentioned above, but the password is usually removed).
  4. The SecurityContextHolder security context container sets the Authentication filled with information in step 3 through the SecurityContextHolder.getContext().setAuthentication(...) method.
  5. It can be seen that the AuthenticationManager interface (authentication manager) is the core interface related to authentication and the starting point for initiating authentication. Its implementation class is ProviderManager. Spring Security supports multiple authentication methods, so ProviderManager maintains a List to store multiple authentication methods. In the end, the actual authentication work is completed by AuthenticationProvider. We know that the corresponding AuthenticationProvider implementation class of the web form is DaoAuthenticationProvider, which maintains a UserDetailsService internally and is responsible for obtaining UserDetails. Finally, the AuthenticationProvider populates the UserDetails into the Authentication.

Three, even the database

1. Entity class information

@Data
@TableName("xc_user")
public class XcUser implements Serializable {
    
    

    private static final long serialVersionUID = 1L;

    private String id;

    private String userName;

    private String password;

    private String salt;

    private String name;
    private String nickname;
    private String wxUnionid;
    private String companyId;
    /**
     * 头像
     */
    private String userpic;

    private String utype;

    private Timestamp birthday;

    private String sex;

    private String email;

    private String cellphone;

    private String qq;

    /**
     * 用户状态
     */
    private String status;

    private Timestamp createTime;

    private Timestamp updateTime;

}

2. Database table

DROP TABLE IF EXISTS `xc_user`;
CREATE TABLE `xc_user`  (
  `id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `user_name` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `password` varchar(96) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `salt` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `wx_unionid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '微信unionid',
  `nickname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '昵称',
  `name` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `userpic` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '头像',
  `company_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `utype` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `birthday` datetime NULL DEFAULT NULL,
  `sex` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `email` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `cellphone` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `qq` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `status` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户状态',
  `create_time` datetime NOT NULL,
  `update_time` datetime NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `unique_user_username`(`user_name`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

3、mapper

@Mapper
public interface XcUserMapper extends BaseMapper<XcUser> {
    
    

}

4. Implement the UserDetailsService interface and override the loadUserByUsername method

package com.pzz.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.pzz.domain.LoginUser;
import com.pzz.mapper.XcUserMapper;
import com.pzz.po.XcUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

/**
 * 用户验证处理
 *
 * @author ruoyi
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    
    
    private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);

    @Autowired
    private XcUserMapper xcUserMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
        LambdaQueryWrapper<XcUser> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(XcUser::getUserName, username);
        XcUser xcUsers = xcUserMapper.selectOne(lambdaQueryWrapper);
        //如果沒有用戶就拋出异常
        if (ObjectUtils.isEmpty(xcUsers)) {
    
    
            throw new RuntimeException("用户名或密码错误");
        }
        //查到用户,返回信息
        return new LoginUser(xcUsers);
    }
}

5. Encapsulate the returned UserDetails

@Data
public class LoginUser implements UserDetails
{
    
    
    private static final long serialVersionUID = 1L;

    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 部门ID
     */
    private Long deptId;

    /**
     * 用户唯一标识
     */
    private String token;

    /**
     * 登录时间
     */
    private Long loginTime;

    /**
     * 过期时间
     */
    private Long expireTime;

    /**
     * 登录IP地址
     */
    private String ipaddr;

    /**
     * 登录地点
     */
    private String loginLocation;

    /**
     * 浏览器类型
     */
    private String browser;

    /**
     * 操作系统
     */
    private String os;

    /**
     * 权限列表
     */
    private Set<String> permissions;

    /**
     * 用户信息
     */
    private XcUser user;

    public LoginUser()
    {
    
    
    }

    public LoginUser(XcUser user)
    {
    
    
        this.user = user;
    }

    public LoginUser(XcUser user, Set<String> permissions)
    {
    
    
        this.user = user;
        this.permissions = permissions;
    }

    public LoginUser(Long userId, Long deptId, XcUser user, Set<String> permissions)
    {
    
    
        this.userId = userId;
        this.deptId = deptId;
        this.user = user;
        this.permissions = permissions;
    }

    @Override
    public String getPassword()
    {
    
    
        return user.getPassword();
    }

    @Override
    public String getUsername()
    {
    
    
        return user.getUserName();
    }

    /**
     * 账户是否未过期,过期无法验证
     */
    @Override
    public boolean isAccountNonExpired()
    {
    
    
        return true;
    }

    /**
     * 指定用户是否解锁,锁定的用户无法进行身份验证
     * 
     * @return
     */
    @Override
    public boolean isAccountNonLocked()
    {
    
    
        return true;
    }

    /**
     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
     * 
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired()
    {
    
    
        return true;
    }

    /**
     * 是否可用 ,禁用的用户不能身份验证
     * 
     * @return
     */
    @Override
    public boolean isEnabled()
    {
    
    
        return true;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities()
    {
    
    
        return null;
    }
}

6. Plain text login

6.1. Set plaintext

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
	    @Bean
    public PasswordEncoder passwordEncoder() {
    
    
        //密码为明文方式
        return NoOpPasswordEncoder.getInstance();
//        return new BCryptPasswordEncoder();
    }
}

6.2. Test

insert image description here

insert image description here

7. Password encryption login

7.1. Set password encryption

    @Bean
    public PasswordEncoder passwordEncoder() {
    
    
//        //密码为明文方式
//        return NoOpPasswordEncoder.getInstance();
        return new BCryptPasswordEncoder();
    }

7.2. Generate password and store in database

insert image description hereinsert image description here

7.3. Test, login successful

insert image description here

8. Pay attention to remove the customized username and password.

insert image description here

4. Integrate jwt and return user information after successful login

1. Introduce dependencies

		<!-- redis 缓存操作 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

		<!-- JWT-->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.5.1</version>
		</dependency>

2. JwtUtils tool class

package com.pzz.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;

public class JwtUtils {
    
    
  
    private static final String SECRET_KEY = "yourSecretKey";
    private static final long EXPIRATION_TIME = 86400000; // 过期时间为1天

    /**
     * 用于生成JWT。
     * 它接收一个用户ID作为参数,并使用内置的算法和秘钥生成一个包含用户ID和过期时间的签名。
     * @param userId
     * @return
     */
    public static String generateToken(String userId) {
    
    
        Date expirationDate = new Date(System.currentTimeMillis() + EXPIRATION_TIME);
        return Jwts.builder()
                .setSubject(userId)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                .compact();
    }

    /**
     * 用于从JWT中提取用户ID。
     * 它接收一个JWT令牌作为参数,并解析其中的主题(主体)部分来获取用户ID。
     * @param token
     * @return
     */
    public static String getUserIdFromToken(String token) {
    
    
        Claims claims = Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();

        return claims.getSubject();
    }

    /**
     * 用于验证JWT的有效性。
     * 它接收一个JWT令牌作为参数,并解析和验证签名。
     * 如果令牌有效且未过期,则返回true;否则返回false。
     * @param token
     * @return
     */
    public static boolean isTokenValid(String token) {
    
    
        try {
    
    
            Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token);
            return true;
        } catch (Exception e) {
    
    
            return false;
        }
    }
}

3. redis tool class

package com.pzz.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * spring redis 工具类
 *
 * @author ruoyi
 **/
@SuppressWarnings(value = {
    
     "unchecked", "rawtypes" })
@Component
public class RedisCache
{
    
    
    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
    
    
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     * @param timeout 时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
    {
    
    
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout)
    {
    
    
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @param unit 时间单位
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit)
    {
    
    
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获取有效时间
     *
     * @param key Redis键
     * @return 有效时间
     */
    public long getExpire(final String key)
    {
    
    
        return redisTemplate.getExpire(key);
    }

    /**
     * 判断 key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public Boolean hasKey(String key)
    {
    
    
        return redisTemplate.hasKey(key);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key)
    {
    
    
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
    
    
        return redisTemplate.delete(key);
    }

    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     * @return
     */
    public boolean deleteObject(final Collection collection)
    {
    
    
        return redisTemplate.delete(collection) > 0;
    }

    /**
     * 缓存List数据
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList)
    {
    
    
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key)
    {
    
    
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存Set
     *
     * @param key 缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
    {
    
    
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext())
        {
    
    
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key)
    {
    
    
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
    {
    
    
        if (dataMap != null) {
    
    
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key)
    {
    
    
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 往Hash中存入数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
    {
    
    
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 获取Hash中的数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getCacheMapValue(final String key, final String hKey)
    {
    
    
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }

    /**
     * 获取多个Hash中的数据
     *
     * @param key Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
    {
    
    
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    /**
     * 删除Hash中的某条数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return 是否成功
     */
    public boolean deleteCacheMapValue(final String key, final String hKey)
    {
    
    
        return redisTemplate.opsForHash().delete(key, hKey) > 0;
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern)
    {
    
    
        return redisTemplate.keys(pattern);
    }
}

4. redis configuration

spring:
  redis:
    host: 127.0.0.1
    post: 6379

5. Create authenticationManagerBean object and configure security interception mechanism

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
    
    
        return super.authenticationManagerBean();
    }

	//配置安全拦截机制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http     //关闭csrf
                .csrf().disable()
                //不通过session获取securityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                //放行登录接口
                .antMatchers("/login").anonymous()
                //除了上面的请求,其他的请求必须授权认证
                .anyRequest().authenticated();
    }
}

6. Login interface

    /**
     * 登录的方法
     * @return
     */
    @PostMapping("/login")
    public R loginSuccess(@RequestBody XcUser xcUser) {
    
    
        return loginService.login(xcUser);
    }

7. Implementation of login, using user ID to generate jwt

@Service
public class LoginServiceImpl implements LoginService {
    
    

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisCache redisCache;

    /**
     * 登录认证的方法
     * @param xcUser
     * @return
     */
    @Override
    public R login(XcUser xcUser) {
    
    
        //Authentication authenticate 进行用户认证
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(xcUser.getUserName(), xcUser.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);

        //认证失败,返回异常
        if (ObjectUtils.isEmpty(authenticate)){
    
    
            throw  new RuntimeException("登录失败...");
        }
        //认证通过,生成jwt,返回
        LoginUser loginUser = (LoginUser)authenticate.getPrincipal();
        String userId = loginUser.getUser().getId().toString();
        //根据用户ID生成jwt
        String jwt = JwtUtils.generateToken(userId);
        Map<String,String> map = new HashMap<>();
        map.put("token",jwt);
        //把完整的用户信息存入redis,userId作为key
        redisCache.setCacheObject("login:"+userId,loginUser);
        String username = loginUser.getUser().getUserName().toString();
        return R.ok(map,"登录成功,欢迎:"+username);
    }
}

8. Test interface

insert image description here

5. Certification filter

When the user logs in, the generated token information is stored in the headers. Each request should carry the token as a verification of whether to log in.

1. Authentication filter JwtAuthenticationTokenFilter

package com.pzz.filter;

import com.pzz.domain.LoginUser;
import com.pzz.utils.JwtUtils;
import com.pzz.utils.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.Objects;

/**
 * @author pzz
 * @date 2023/7/26 22:51
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    
    

    @Autowired
    private RedisCache redisCache;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
    
        //获取token
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)){
    
    
            //放行
            filterChain.doFilter(request,response);
            return;
        }
        //解析token
        String userId;
        try {
    
    
            userId = JwtUtils.getUserIdFromToken(token);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }

        //从redis中获取用户信息
        String redisKey = "login:"+userId;
        LoginUser loginUser = (LoginUser)redisCache.getCacheObject(redisKey);
        if (Objects.isNull(loginUser)){
    
    
            throw new RuntimeException("用户未登录");
        }

        //存入securContextHolder
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,
                null, null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(request,response);
    }
}

2. Configure authentication filters

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
	  @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

	  @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
		//配置认证过滤器
    	http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
	}
}

3. How to query user information based on ID

    /**
     * 根据用户ID查询用户信息
     * @param id
     * @return
     */
    @RequestMapping("/user/{id}")
    public R getuser(@PathVariable("id") String id) {
    
    
        XcUser xcUser = userMapper.selectById(id);
        return R.ok(xcUser);
    }

4. Test

4.1. No token carried

insert image description here

4.2. Carrying token

insert image description here

5. Log out

5.1, controller code

    /**
     * 注销登录
     */
    @GetMapping("/logout")
    public R logout(){
    
    
        return loginService.logout();
    }

5.2, service code

    /**
     * 退出登录
     * @return
     */
    @Override
    public R logout() {
    
    
        //获取SecurityContextHolder中的用户ID
        UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser)authenticationToken.getPrincipal();
        String userId = loginUser.getUser().getId();
        //删除redis中的值
        redisCache.deleteObject("login:"+userId);
        return R.ok(null,"注销成功");
    }

6. Authorization

1. Related configuration

1.1. Enable the permissions required to access resources

@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    }

1.2. Access path setting permissions

    @RequestMapping("/hello")
    @PreAuthorize("hasAuthority('test')")//拥有test权限方可访问
    public String hello() {
    
    
        return "hello";
    }

2. Encapsulate permissions and add permissions to users

2.1. Encapsulate the user’s entity class and set permissions for the user

@Data
public class LoginUser implements UserDetails{
    
    
	    /**
     * 权限列表
     */
    private Set<String> permissions;

	public LoginUser(XcUser user, Set<String> permissions)
    {
    
    
        this.user = user;
        this.permissions = permissions;
    }

	@JSONField(serialize = false)
    private List<SimpleGrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities()
    {
    
    
        if (authorities != null){
    
    
            return authorities;
        }
        authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
        return authorities;
    }
}

2.2. Query the user's permissions when the user logs in. The user's permissions are currently set to test.

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    
    
	    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
        //查询用户的权限
        Set<String> set = new HashSet<>();
        set.add("test");
        //查到用户,返回信息
        return new LoginUser(xcUsers,set);
    }
}

2.3. Filter interception permissions

insert image description here

3. Test

3.1. After logging in, visit hello

insert image description here

3.2. Modify the access permission of hello to test111 and test

insert image description here
insert image description here

4. Check user permissions from data

Permissions generally use five tables

  1. user table
  2. role table
  3. user_role_intermediate table
  4. Menu table (authority table)
  5. role_permission_intermediate table

4.1. SQL for querying permissions

<?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.pzz.mapper.MenuMapper">

    <select id="selectPermsByUserId" resultType="java.lang.String" parameterType="integer">
        SELECT
            DISTINCT
            sm.perms
        from
            sys_user_role sur
        left join sys_role sr on sur.role_id = sr.role_id
        LEFT JOIN sys_role_menu srm on sr.role_id = srm.role_id
        left join sys_menu sm on srm.menu_id = sm.menu_id
        where
            sm.perms is not null
            and sm.perms != ""
            and sur.user_id = #{userId}
    </select>
</mapper>

4.2. When the login is successful, set the user's permissions

insert image description here

4.3. Modify the permission to access the hello interface

insert image description here

4.4. Access test

insert image description here

Seven, cross-domain

1. Springboot settings cross-domain (omitted)

2. Cross-domain security settings

 //设置跨域
 http.cors();

8. Security certification process

insert image description here

Finish!
hy:23


							真正的幸福来自于内心的满足和积极的生活态度。

Guess you like

Origin blog.csdn.net/weixin_49107940/article/details/131258029