用户登录及鉴权
6 分钟 21803 字 |
基于SpringBoot Security 与 JWT 的用户登录及鉴权
1 JWT 工具类
public class JwtTokenUtils {
/**
* 用户请求头中的Token的key(例:Authorization: *************)
*/
public static final String TOKEN_HEADER = "Authorization";
/**
* 返回的Token前缀信息(自定义配置)
*/
public static final String TOKEN_PREFIX = "Bearer ";
/**
* JWT的加密算法SigningKey
*/
private static final String SECRET = "jwtSecret";
/**
* 该JWT的签发者,是否使用是可选的(可以使用项目名称或者作者名称之类)
*/
private static final String ISS = "DEC";
/**
* 角色的key
*/
private static final String ROLE_CLAIMS = "rol";
/**
* 过期时间是7天
*/
private static final long EXPIRATION = 604800L;
// private static final long EXPIRATION = 5*60;
/**
* 创建token方法
* @param username 用户名
* @param role 角色权限集合
* @param
* @return
*/
public static String createToken(String username, List<String> role) {
HashMap<String, Object> map = new HashMap<>();
map.put(ROLE_CLAIMS, role);
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512, SECRET)
.setClaims(map)
.setIssuer(ISS)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION * 1000))
.compact();
}
/**
* 从token中获取用户名
* @param token
* @return
*/
public static String getUserName(String token){
return getTokenBody(token).getSubject();
}
/**
* 从token中获取用户角色集合
* @param token
* @return
*/
public static List<String> getUserRole(String token){
return (List<String>) getTokenBody(token).get(ROLE_CLAIMS);
}
/**
* 判断是否已过期
* @param token
* @return
*/
public static boolean isExpiration(String token) {
try {
return getTokenBody(token).getExpiration().before(new Date());
} catch (ExpiredJwtException e) {
return true;
}
}
/**
* 获取token请求体内容
* @param token
* @return
*/
private static Claims getTokenBody(String token){
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
}
}
2 数据层
实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type= IdType.AUTO)
private Long id;
private String username;
private String password;
private Set<String> roles;
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", roles=" + roles +
'}';
}
}
//DTO
public class JwtUser implements UserDetails {
/**
* 用户ID
*/
private Long id;
/**
* 用户名
*/
private String userName;
/**
* 密码
*/
private String passWord;
/**
* 权限信息
*/
private Collection<? extends GrantedAuthority> authorities;
/**
* 使用user创建jwtUser的构造器
* @param user 用户对象
*/
public JwtUser(User user) {
id = user.getId();
userName = user.getUsername();
passWord = user.getPassword();
if(user.getRoles() != null && user.getRoles().size() > 0){
List<GrantedAuthority> list = new ArrayList<>();
for (String role : user.getRoles()) {
list.add(new SimpleGrantedAuthority(role));
}
authorities = list;
}
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return passWord;
}
@Override
public String getUsername() {
return userName;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public String toString() {
return "JwtUser{" +
"id=" + id +
", userName='" + userName + '\'' +
", passWord='" + passWord + '\'' +
", authorities=" + authorities +
'}';
}
}
UserDetailsService
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
User user = userService.getUserByUsername(name);
Set<String> roles = userMapper.selectRolesByUsername(name);
user.setRoles(roles);
return new JwtUser(user);
}
}
Mapper
@Mapper
public interface UserMapper extends BaseMapper<User> {
@Select("select COUNT(*) from user")
Integer listNumUser();
@Select("select * from user where username = #{username}")
User selectByUsername(String username);
@Select("select roles from user where username = #{username}")
Set<String> selectRolesByUsername(String username);
@Insert("INSERT INTO user(`username`,`password`,`roles`)\n" +
"values\n" +
"(#{user.username},#{user.password},'${roles}');")
int insertUser(User user,String roles);
3 Filter
登录拦截器
/**
* 登录拦截器 - 请求登录接口/auth/login时会被拦截
* * 调用attemptAuthentication方法获取登录请求的(用户名,密码)等信息,
* * 调用authenticationManager.authenticate()让spring-security去进行验证
* * JWTAuthenticationFilter继承于UsernamePasswordAuthenticationFilter
* * 该拦截器用于获取用户登录的信息,只需创建一个token并调用authenticationManager.authenticate()让spring-security去进行验证就可以了,
* * 不用自己查数据库再对比密码了,这一步交给spring去操作。 这个操作有点像是shiro的subject.login(new UsernamePasswordToken()),
* * 验证的事情交给框架
*/
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
/**
* 自定义登录URL(使用Security默认的登录地址为/login) - 根据项目需求配置
* @param authenticationManager
*/
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
super.setFilterProcessesUrl("/auth/login");
}
/**
* 第一步:获取用户参数,并进行校验(用户名密码)
* @param request
* @param response
* @return
* @throws AuthenticationException
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
/**从输入流中获取到登录的信息*/
User loginUser = new ObjectMapper().readValue(request.getInputStream(), User.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword(), new ArrayList<>())
);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* 第二步:第一步方法成功验证后,则生成token并返回
* @param request
* @param response
* @param chain
* @param authResult
* @throws IOException
* @throws ServletException
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
/**获取上一步验证后的JwtUser对象*/
JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
// System.out.println(jwtUser);
List<String> role = new ArrayList<>();
Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities();
if(authorities != null && authorities.size() > 0){
for (GrantedAuthority authority : authorities){
role.add(authority.getAuthority());
}
}
/**根据jwtUser信息该创建一个token*/
String token = JwtTokenUtils.createToken(jwtUser.getUsername(), role);
/**
* 1.返回创建成功的token
* 2.按照jwt的规定,最后请求的时候应该是 "Bearer token"
* 3.返回token加上"Bearer "前缀
*/
response.setHeader("token", JwtTokenUtils.TOKEN_PREFIX + token);
PrintWriter writer = response.getWriter();
//字符流输出
writer.write(JwtTokenUtils.TOKEN_PREFIX + token);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.getWriter().write("authentication failed, reason: " + failed.getMessage());
}
}
Token解析及用户鉴权拦截器
/**
* @Description:处理所有HTTP请求,并检查是否存在带有正确令牌的Authorization标头
*/
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
/**
* 过滤器拦截方法
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
/**
* 获取请求头中的token
*/
String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
/**如果请求头中没有Authorization信息则直接执行放行(或者请求头中没有TOKEN_PREFIX前缀)*/
if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
try {
/**如果请求头中有token,则进行解析,并且设置认证信息*/
SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
} catch (TokenIsExpiredException e) {
/**返回json形式的错误信息*/
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
String reason = "统一处理,原因:" + e.getMessage();
response.getWriter().write(new ObjectMapper().writeValueAsString(reason));
response.getWriter().flush();
return;
}
super.doFilterInternal(request, response, chain);
}
/**
* 将token进行解析,获取用户信息,并且设置认证信息(对Token进行校验)
* @param tokenHeader
* @return
* @throws TokenIsExpiredException
*/
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) throws TokenIsExpiredException {
String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
boolean expiration = JwtTokenUtils.isExpiration(token);
if (expiration) {
throw new TokenIsExpiredException("token超时了");
} else {
String username = JwtTokenUtils.getUserName(token);
List<String> roles = JwtTokenUtils.getUserRole(token);
if (username != null) {
List<GrantedAuthority> list = new ArrayList<>();
if(roles != null && roles.size() > 0){
for (String role : roles) {
list.add(new SimpleGrantedAuthority(role));
}
}
return new UsernamePasswordAuthenticationToken(username, null, list);
}
}
return null;
}
}
Exception
/**
* @Description:没有访问权限
*/
public class JWTAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
String reason = "统一处理,原因(没有访问权限):" + e.getMessage();
httpServletResponse.getWriter().write(new ObjectMapper().writeValueAsString(reason));
}
}
/**
* @Description:没有携带token或者token无效
*/
public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
String reason = "统一处理,原因(无token或token无效):" + authException.getMessage();
response.getWriter().write(new ObjectMapper().writeValueAsString(reason));
}
}
/**
* @Description:Token过期
*/
public class TokenIsExpiredException extends Exception {
public TokenIsExpiredException() {}
public TokenIsExpiredException(String message) {
super(message);
}
public TokenIsExpiredException(String message, Throwable cause) {
super(message, cause);
}
public TokenIsExpiredException(Throwable cause) {
super(cause);
}
public TokenIsExpiredException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
SecurityConfig
/**
* @Author: Tian Yang
* @Date: 2022-08-22
* @Description:
* * 注释:
* * 注解@EnableWebSecurity开启Security功能
* * 注解@EnableGlobalMethodSecurity(prePostEnabled = true),Security默认是禁用注解的,要想开启注解需要此配置,其表达式时间方法级别的安全性4个注解:(
* * @PreAuthorize 在方法调用之前,基于表达式的计算结果来限制对方法的访问 例如:@PreAuthorize("hasAuthority('ADMIN')")表示用户具有admin角色,才能访问该方法
* * @PostAuthorize 允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常
* * @PostFilter 允许方法调用,但必须按照表达式来过滤方法的结果
* * @PreFilter 允许方法调用,但必须在进入方法之前过滤输入值 )
*
**/
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("userDetailsServiceImpl")
private UserDetailsService userDetailsService;
/**
* 对密码进行加密的类,配置在spring中,方便调用
* 例如:bCryptPasswordEncoder.encode(registerUser.get("password"))
* @return
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
/**
* 重写configure方法,根据项目需求配置,并将将写好的相应配置类引入
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
/**cors()开启跨域*/
http.cors()
.and()
/**禁用CSRF保护*/
.csrf().disable()
/**http.authorizeRequests()方法添加多个子项(.antMatchers(****))来指定URL的自定义要求*/
.authorizeRequests()
/**请求路径下的DELETE请求需要"ADMIN"权限才能操作,也可在接口上@PreAuthorize("hasAuthority('ADMIN')")*/
.antMatchers(HttpMethod.DELETE, "/task/removeTaskList").hasAuthority("ADMIN")
/**需要验证的资源*/
// .antMatchers("/login/**").authenticated()
/**匿名访问,无需鉴权*/
.antMatchers("/auth/register").anonymous()
/**静态资源放行*/
.antMatchers("/**/*.png","/task/addTask","/step/addStep",
"/monitor/*").permitAll()
/**对http所有的请求必须通过授权认证才可以访问*/
.anyRequest().authenticated()
/**放行所有请求*/
// .anyRequest().permitAll()
.and()
/**添加自定义的登录拦截方式(JWTAuthenticationFilter配置类) 注意:顺序:JWTAuthenticationFilter必须在JWTAuthorizationFilter之前*/
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
/**添加自定义的请求拦截方式(JWTAuthorizationFilter配置类)*/
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
/**不需要session*/
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
/**自定义异常处理(没有携带token或者token无效,JWTAuthenticationEntryPoint类配置处理)*/
.exceptionHandling().authenticationEntryPoint(new JWTAuthenticationEntryPoint())
/**添加无权限时的处理(JWTAccessDeniedHandler类配置无访问权限异常处理)*/
.accessDeniedHandler(new JWTAccessDeniedHandler());
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
跨域问题
- 放在根目录
@Configuration
public class CorsConfig {
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*"); // 1允许任何域名使用
corsConfiguration.addAllowedHeader("*"); // 2允许任何头
corsConfiguration.addAllowedMethod("*"); // 3允许任何方法(post、get等)
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig());
return new CorsFilter(source);
}
}
- 放在config文件
@Configuration
public class CorsConfig implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest reqs = (HttpServletRequest) req;
String curOrigin = reqs.getHeader("Origin");
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", curOrigin == null ? "true" : curOrigin);
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, HEAD");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "access-control-allow-origin, authority, content-type, version-info, X-Requested-With");
response.setContentType("application/json;charset=UTF-8");
chain.doFilter(req, res);
}
@Override
public void init(FilterConfig filterConfig) {}
@Override
public void destroy() {}
}
~ ~ The End ~ ~
分类标签:技术,springboot
文章标题:用户登录及鉴权
文章链接:http://120.46.217.131:82/archives/46/
最后编辑:2022 年 10 月 12 日 16:33 By Yang
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
文章标题:用户登录及鉴权
文章链接:http://120.46.217.131:82/archives/46/
最后编辑:2022 年 10 月 12 日 16:33 By Yang
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)