关于spring:五整合redisrestful接前后端分离进行登录验证和url鉴权

2次阅读

共计 11934 个字符,预计需要花费 30 分钟才能阅读完成。

应用 jwt token 来认证和鉴权
<!–jwt 配置文件 –>
<dependency>

<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>

</dependency>

(jwt 也可,只须要将 userDto 的信息应用 jwt 来保留即可返回时,返回 jwt 的字符串,我这是将用户信息保留在了 redis 中)

1、配置 security 文件

/**

  • SpringSecurity 的配置
  • Created by wenye on 2021/4/12.
    */

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
@Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
// 下面是登录认证相干  上面为 url 权限相干 - ========================================================================================

/**
 * 获取拜访 url 所须要的角色信息
 */
@Autowired
private  UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource;
/**
 * 认证权限解决 - 将下面所取得角色权限与以后登录用户的角色做比照,如果蕴含其中一个角色即可失常拜访
 */
@Autowired
private  UrlAccessDecisionManager urlAccessDecisionManager;
/**
 * 自定义拜访无权限接口时 403 响应内容
 */
@Autowired
private  UrlAccessDeniedHandler urlAccessDeniedHandler;

@Autowired
private MyuserDetailsService userDetailsService;

// 申请权限配置
@Override
protected void configure(HttpSecurity http) throws Exception {http.csrf().disable()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            .antMatchers("/login/user/employee/login", "/login/user/employee/register")// 对登录注册要容许匿名拜访
            .permitAll()
            .antMatchers(HttpMethod.OPTIONS)// 跨域申请会先进行一次 options 申请
            .permitAll()
            .authenticated();
    ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
    registry.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                                         @Override
                                         public <O extends FilterSecurityInterceptor> O postProcess(O o) {o.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource);
                                             o.setAccessDecisionManager(urlAccessDecisionManager);
                                             return o;
                                         }
                                     });
    // 增加 JWT filter
    http.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    // 增加自定义未受权和未登录后果返回
    http.exceptionHandling()
            .accessDeniedHandler(restfulAccessDeniedHandler)
            .authenticationEntryPoint(restAuthenticationEntryPoint);
}

@Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {return new JwtAuthenticationTokenFilter();
}

/* 能够不要哦

@Override
public void configure(WebSecurity web) throws Exception {
    // 设置拦挡疏忽文件夹,能够对动态资源放行
    web.ignoring().antMatchers("/css/**", "/js/**");
}*/

}

2、usercontroller
@RestController
@RequestMapping(“/login/user”)
@Api(tags = “ 用户体系 ”)
public class SysUserController {

@Autowired
private ITbSysEmployeeService tbSysEmployeeService;

@Autowired
private RedisToolCache redisToolCache;

@Autowired
private RedisTemplate redisTemplate;
/**
 * 员工登录
 * @param dto
 * @return
 */
@RequestMapping("/employee/login")
public RespMessage employeelogin(@RequestBody LoginDto dto){
    // 数据校验 @Valid
    RespMessage respMessage = tbSysEmployeeService.employeelogin(dto, TokenChannelEnum.WEB.getValue());
    try {beanLife();
    } catch (Exception e) {e.printStackTrace();
    }
    if (ObjectUtils.isEmpty(respMessage)){return RespHandler.failure("账户名明码谬误");
    }
    return respMessage;
}

}

3、UserDot 存储直达用户信息
@Data
public class UserDto implements Serializable {

private String phone;
private String userId;
private String passwd;
private String validCode;
private String module;// 登陆的模块
private String userName;// 登录用户名
private Long rentId;// 租户 ID(指定租户 ID 登录)
private String appType;// 利用类型:1Android,2IOS,private String newPasswd;
private Integer channel;
private String token;
private List<String> roles;
private String avatar;

}

4、userserviceImpl
@Service
public class TbSysEmployeeServiceImpl extends ServiceImpl<TbSysEmployeeDao, TbSysEmployeeEntity> implements ITbSysEmployeeService {

@Autowired
private TbSysEmployeeDao tbSysEmployeeDao;

@Autowired
private TokenManager tokenManager;
@Override
public RespMessage  employeelogin(LoginDto dto, int channel) {if (ObjectUtils.isEmpty(dto)) {return null;}
    TbSysEmployeeEntity employee1 = tbSysEmployeeDao.employeeOne(dto.getUserName());
    if (ObjectUtils.isEmpty(employee1)) {return null;}
    // 校验明码
    String pwd = "";
    try {pwd = EncryptionUtil.SHA1(EncryptionUtil.SHA1(dto.getPassword()+employee1.getSalt()));
    } catch (Exception e) {e.printStackTrace();
    }
    if (! pwd.equals( employee1.getPassword())) {return null;}
// 生成 token、存储日志
    UserDto userDto = new UserDto();
    userDto.setUserId(employee1.getId());
    userDto.setModule(String.valueOf(channel));
    userDto.setUserName(employee1.getNickName());
    userDto.setPhone(employee1.getPhone());
    List<Integer> list = CommonUtils.intToIntegerList(employee1.getRole());
    List<String> roles = new ArrayList<>();
    for (Integer i : list) {if (i == 1) {roles.add("admin");
        }else {roles.add("");
        }
    }
    userDto.setRoles(roles);
    userDto.setAvatar(employee1.getAvatar());
    // 生成 token
    String token = tokenManager.createToken(userDto);
    TbSysLoginLogEntity entity = new TbSysLoginLogEntity();
    entity.setUserName(employee1.getName());
    entity.setPhone(employee1.getPhone());
    entity.setOperateType(1);
    entity.setDeviceType(1);
    tbSysLoginLogService.save(entity);
    userDto.setToken(token);
    return RespHandler.success(userDto);
}

}

5、tokenmanger
就是 reids 的创立,获取,校验,返回 1 个 32 位的 UUID 字符串就行

6、ITbSysEmployeeService 员工信息查问
public class TbSysEmployeeImpl extends ServiceImpl<TbSysEmployeeDao, TbSysEmployeeEntity> implements ITbSysEmployeeService {

@Autowired
private TbSysEmployeeDao tbSysEmployeeDao;

@Override
public TbSysEmployeeEntity selectByUsername(String username) {


    TbSysEmployeeEntity entity = null;
    try {entity = tbSysEmployeeDao.employeeOne(username);
    } catch (Exception e) {e.printStackTrace();
    }
    return entity;
}

}

对应的 sql 只有获取用户名称,明码,权限列表就 ok 了,这里 nick_name 为用户名,
SELECT
se.id,
se.name,
se.phone,
se.card,
se.password,
se.status,
se.nick_name,
se.code,
se.address,
se.create_name,
se.create_time,
se.update_time,
se.salt,
se.avatar,
sr.ROLETYPE_ID role
FROM tb_sys_employee se
left join tb_sys_user_role sur on sur.USER_ID = se.id
left join tb_sys_role sr on sr.id = sur.role_id
WHERE 1 = 1
<if test=”userName != null and userName != ””>

and se.nick_name = #{userName}

</if>

7、JwtAuthenticationTokenFilter token 过滤器
我这只是通过 token 去 redis 中查问用户信息,如果用 jwt 可间接解析字符串中用户信息
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

@Autowired
private MyuserDetailsService userDetailsService;

@Autowired
private TokenManager tokenManager;

@Autowired
private JwtTokenUtil jwtTokenUtil;

// @Value(“${jwt.tokenHeader}”)

private String tokenHeader = "Authorization";

// @Value(“${jwt.tokenHead}”)

private String tokenHead = "Bearer";

@Override
protected void doFilterInternal(HttpServletRequest request,
                                HttpServletResponse response,
                                FilterChain chain) throws ServletException, IOException {
    // 获取 token
    String authHeader = request.getHeader(this.tokenHeader);
    // 解析 token
    if (authHeader != null && tokenManager.checkToken(authHeader)) {
        // 解析 token 获取用户名称
        UserDto userDto = (UserDto) tokenManager.getToken(authHeader);
        String userName = userDto.getUserName();
        Authentication authentication1 = SecurityContextHolder.getContext().getAuthentication();
        if (userName != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            // 返回 security 用户受权对象
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(userName);
            // 判断 token 是否生效
            if (tokenManager.checkToken(authHeader)) {UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                // 资源 的起源存储
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                //Authentication 对象退出到上下文中期待 ProviderManager 去去解决验收信息
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
    }
    // 转发给过滤器链高低一个对象。这里的下一个指的是下一个 filter,chain.doFilter(request, response);
}

}

8、通过用户名称获取用户信息和权限
/**

  • @author:wenye
  • @date:Created in 2021/4/7 11:49
  • @description:校验身份
  • @version: 1.0.0$
    */

@Component
public class MyuserDetailsService implements UserDetailsService {

private Logger logger = LoggerFactory.getLogger(MyuserDetailsService.class);

@Autowired
private ITbSysEmployeeService tbSysEmployeeService;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {TbSysEmployeeEntity entity = tbSysEmployeeService.selectByUsername(username);
    // 校验明码
    if (ObjectUtils.isEmpty(entity)) {throw new BadCredentialsException("请输出正确用户名");

    }
    // 生成 token、存储日志
    UserDto userDto = new UserDto();
    userDto.setUserId(entity.getId());
    userDto.setUserName(entity.getNickName());
    userDto.setPhone(entity.getPhone());
    userDto.setPasswd(entity.getPassword());
    // 留神这里要革新,骚操作须要更改,此处获取数据库中的用户权限,// 我数据库中存的是组成的二进制组合相加如合乎 1 2 4 8 各自代表一个权限 如:7 就是由 1 2 4 组成而后对应判断权限
    不过太难顶了(保护起来)。Lis\t<Integer> list = CommonUtils.intToIntegerList(entity.getRole());
    List<String> roles = new ArrayList<>();
    for (Integer i : list) {if (i == 1) {roles.add("admin");
        }else {roles.add("");
        }
    }
    userDto.setRoles(roles);
    userDto.setAvatar(entity.getAvatar());
    // 据用户名查找用户信息,用户名和明码
    //AuthorityUtils.commaSeparatedStringToAuthorityList("admin") 把字符串转化为对应的对象,以逗号分隔字符串
    List<String> roles1 = userDto.getRoles();
    // 解析
    StringBuilder rolestr = new StringBuilder();
    roles1.stream().forEach(e->{rolestr.append(e+",");
    });

    logger.info("用户名为:{}",username);
    return new User(username,userDto.getPasswd(), AuthorityUtils.commaSeparatedStringToAuthorityList(rolestr.toString()));
}

}

9、用户名,明码校验
@Component
public class MyPasswordEncoder implements PasswordEncoder {

private Logger logg = LoggerFactory.getLogger(MyPasswordEncoder.class);

@Autowired
private ITbSysEmployeeService tbSysEmployeeService;
/**
 *  编码原始明码。通常,良好的编码算法利用 SHA- 1 或更大的哈希与 8 字节或更大的随机生成的盐相结合。* @param rawPassword 明码,一个可读的字符值序列
 * @return
 */
@Override
public String encode(CharSequence rawPassword) {logg.info("原始明码:{}", rawPassword);
    return rawPassword.toString();}

/**
 * 验证从存储中取得的编码明码是否与提交的原始明码匹配。如果明码匹配,返回 true; 如果不匹配,返回 false。存储的明码自身永远不会被解码。* @param username 预设的验证明码。要编码和匹配的原始明码
 * @param encodedPassword 表单输出的明码。来自存储的编码明码与之比拟
 * @return
 */
@Override
public boolean matches(CharSequence username, String encodedPassword) {logg.info("预设的验证明码:{}", username);
    logg.info("表单输出的明码:{}", encodedPassword);
    TbSysEmployeeEntity entity = tbSysEmployeeService.selectByUsername((String) username);
    // 校验明码
    String pwd = "";
    try {pwd = EncryptionUtil.SHA1(EncryptionUtil.SHA1(encodedPassword+entity.getSalt()));
    } catch (Exception e) {e.printStackTrace();
    }
    if (! pwd.equals( entity.getPassword())) {throw new BadCredentialsException("请输出正确的明码");
    }
    return true;
}

}

10、动静 url 权限管制(重点呜呜,坑了我半天)
@Component
public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

@Autowired
TbSysPermissionDao permissionMapper;
/***
 * 返回该 url 所须要的用户权限信息
 *
 * @param object: 贮存申请 url 信息
 * @return: null:标识不须要任何权限都能够拜访
 */
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
    // 获取以后申请 url
    String requestUrl = ((FilterInvocation) object).getRequestUrl();

    // TODO 疏忽 url 请放在此处进行过滤放行,呜呜坑我半天
    List<String> urlList = new ArrayList<>();
    urlList.add("/login/user/employee/login");
    urlList.add("/login/user/employee/register");
    if (urlList.contains(requestUrl)) {return null;}

    // 数据库中所有 url
    List<TbSysPermissionEntity> permissionList = permissionMapper.selectList(null);
    for (TbSysPermissionEntity permission : permissionList) {
        // 获取该 url 所对应的权限
        if (requestUrl.equals(permission.getUrl())) {
            // 据 url 获取对应的角色列表

            List<String> roles = permissionMapper.selectRoleCodeByUrl(permission.getId());
            // 保留该 url 对应角色权限信息,将 role 返回
            return SecurityConfig.createList(roles.toArray(new String[roles.size()]));
        }
    }
    // 如果数据中没有找到相应 url 资源则为非法拜访,要求用户登录再进行操作
    return SecurityConfig.createList("role_login");
}

@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {return null;}

@Override
public boolean supports(Class<?> aClass) {return FilterInvocation.class.isAssignableFrom(aClass);
}

}

11、未登录和未受权的两种解决
/**

  • 当未登录或者 token 生效拜访接口时,自定义的返回后果
  • Created by macro on 2018/5/14.
    */

@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {response.setCharacterEncoding("UTF-8");
    response.setContentType("application/json");
    response.getWriter().println("你还没登录哦");
    response.getWriter().flush();
}

}

12、没得权限
/**

  • 当拜访接口没有权限时,自定义的返回后果
  • Created by macro on 2018/4/26.
    */

@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {

@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {httpServletResponse.setCharacterEncoding("UTF-8");
    httpServletResponse.setContentType("application/json");
    httpServletResponse.getWriter().println("你没有权限哦");
    httpServletResponse.getWriter().flush();
}

}

13、入门小结,前面将会围绕 security 的各种过滤器去进行进一步的剖析和学习

正文完
 0