共计 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 的各种过滤器去进行进一步的剖析和学习