应用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 {
@Autowiredprivate RestfulAccessDeniedHandler restfulAccessDeniedHandler;@Autowiredprivate RestAuthenticationEntryPoint restAuthenticationEntryPoint;// 下面是登录认证相干 上面为url权限相干 - ========================================================================================/** * 获取拜访url所须要的角色信息 */@Autowiredprivate UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource;/** * 认证权限解决 - 将下面所取得角色权限与以后登录用户的角色做比照,如果蕴含其中一个角色即可失常拜访 */@Autowiredprivate UrlAccessDecisionManager urlAccessDecisionManager;/** * 自定义拜访无权限接口时403响应内容 */@Autowiredprivate UrlAccessDeniedHandler urlAccessDeniedHandler;@Autowiredprivate MyuserDetailsService userDetailsService;//申请权限配置@Overrideprotected 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);}@Beanpublic JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() { return new JwtAuthenticationTokenFilter();}
/* 能够不要哦
@Overridepublic void configure(WebSecurity web) throws Exception { // 设置拦挡疏忽文件夹,能够对动态资源放行 web.ignoring().antMatchers("/css/**", "/js/**");}*/
}
2、usercontroller
@RestController
@RequestMapping("/login/user")
@Api(tags = "用户体系")
public class SysUserController {
@Autowiredprivate ITbSysEmployeeService tbSysEmployeeService;@Autowiredprivate RedisToolCache redisToolCache;@Autowiredprivate 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 {
@Autowiredprivate TbSysEmployeeDao tbSysEmployeeDao;@Autowiredprivate TokenManager tokenManager;@Overridepublic 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 {
@Autowiredprivate TbSysEmployeeDao tbSysEmployeeDao;@Overridepublic 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 {
@Autowiredprivate MyuserDetailsService userDetailsService;@Autowiredprivate TokenManager tokenManager;@Autowiredprivate JwtTokenUtil jwtTokenUtil;
// @Value("${jwt.tokenHeader}")
private String tokenHeader = "Authorization";
// @Value("${jwt.tokenHead}")
private String tokenHead = "Bearer";@Overrideprotected 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);@Autowiredprivate ITbSysEmployeeService tbSysEmployeeService;@Overridepublic 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);@Autowiredprivate ITbSysEmployeeService tbSysEmployeeService;/** * 编码原始明码。通常,良好的编码算法利用SHA-1或更大的哈希与8字节或更大的随机生成的盐相结合。 * @param rawPassword 明码,一个可读的字符值序列 * @return */@Overridepublic String encode(CharSequence rawPassword) { logg.info("原始明码:{}", rawPassword); return rawPassword.toString();}/** * 验证从存储中取得的编码明码是否与提交的原始明码匹配。如果明码匹配,返回true;如果不匹配,返回false。存储的明码自身永远不会被解码。 * @param username 预设的验证明码。要编码和匹配的原始明码 * @param encodedPassword 表单输出的明码。来自存储的编码明码与之比拟 * @return */@Overridepublic 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 {
@AutowiredTbSysPermissionDao permissionMapper;/*** * 返回该url所须要的用户权限信息 * * @param object: 贮存申请url信息 * @return: null:标识不须要任何权限都能够拜访 */@Overridepublic 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");}@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() { return null;}@Overridepublic boolean supports(Class<?> aClass) { return FilterInvocation.class.isAssignableFrom(aClass);}
}
11、未登录和未受权的两种解决
/**
- 当未登录或者token生效拜访接口时,自定义的返回后果
- Created by macro on 2018/5/14.
*/
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Overridepublic 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 {
@Overridepublic 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 的各种过滤器去进行进一步的剖析和学习