通常情况下,把API直接暴露出去是风险很大的,
我们一般需要对API划分出一定的权限级别,然后做一个用户的鉴权,依据鉴权结果给予用户对应的API
互联网服务离不开用户认证。一般流程是下面这样。
1、用户向服务器发送用户名和密码。
2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
3、服务器向用户返回一个 session_id,写入用户的 Cookie。
4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。
举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?
一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。
另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。
(引自:阮一峰的网络日志 http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html)
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息
JWT包含了使用.
分隔的三部分:
Header 头部
Payload 负载
Signature 签名
下面是一个JWT的工作流程图。模拟一下实际的流程是这样的(假设受保护的API在/protected中)
1.用户导航到登录页,输入用户名、密码,进行登录
2.服务器验证登录鉴权,如果用户合法,根据用户的信息和服务器的规则生成JWT Token
3.服务器将该token以json形式返回(不一定要json形式,这里说的是一种常见的做法)
4.用户得到token,存在localStorage、cookie或其它数据存储形式中。
5.以后用户请求/protected中的API时,在请求的header中加入 Authorization: Bearer xxxx(token)。此处注意token之前有一个7字符长度的 Bearer
6.服务器端对此token进行检验,如果合法就解析其中内容,根据其拥有的权限和自己的业务逻辑给出对应的响应结果。
7.用户取得结果
Spring Security 是为基于Spring的应用程序提供声明式安全保护的安全性框架。
一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)
两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,
也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。
系统通过校验用户名和密码来完成认证过程。
用户授权指的是验证某个用户是否有权限执行某个操作。
在一个系统中,不同用户所具有的权限是不同的。
比如对一个文件来说,有的用户只能进行读取,
而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,
而每个角色则对应一系列的权限。
对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。
spring.jackson.serialization.indent_output=true //JSON格式化 logging.level.org.springframework.security=info //打印security日志记录
/** * 角色枚举类 */ public enum AuthorityName { ROLE_ADMIN,ROLE_USER }
import java.io.Serializable; public class Authority implements Serializable { private Integer id; private AuthorityName name; public Authority() { } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public AuthorityName getName() { return name; } public void setName(AuthorityName name) { this.name = name; } }
import java.io.Serializable; import java.util.Date; import java.util.List; public class Admins implements Serializable { private Integer aid; private String aname; private String pwd; private Integer aexist; private Integer state; private Integer doid; private String by1; private Date lastPasswordResetDate; public Date getLastPasswordResetDate() { return lastPasswordResetDate; } public void setLastPasswordResetDate(Date lastPasswordResetDate) { this.lastPasswordResetDate = lastPasswordResetDate; } private List<Authority> authorities; public List<Authority> getAuthorities() { return authorities; } public void setAuthorities(List<Authority> authorities) { this.authorities = authorities; } public String getBy1() { return by1; } public void setBy1(String by1) { this.by1 = by1; } public Integer getDoid() { return doid; } public void setDoid(Integer doid) { this.doid = doid; } public Integer getState() { return state; } public void setState(Integer state) { this.state = state; } public Integer getAexist() { return aexist; } public void setAexist(Integer aexist) { this.aexist = aexist; } public Integer getAid() { return aid; } public void setAid(Integer aid) { this.aid = aid; } public String getAname() { return aname; } public void setAname(String aname) { this.aname = aname; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } }
JwtUser + JwtUserFactory + JwtUserDetailsServiceImpl + JwtAuthenticationResponse 需要实现UserDetails接口,用户实体即为Spring Security所使用的用户
/** * 安全服务的用户 * 需要实现UserDetails接口,用户实体即为Spring Security所使用的用户 */ public class JwtUser implements UserDetails { private final Integer id; private final Integer state; private final String username; private final String password; private final String email; private final Collection<? extends GrantedAuthority> authorities; private final boolean enabled; private final Date lastPasswordResetDate; public JwtUser(Integer id, Integer state, String username, String password, String email, Collection<? extends GrantedAuthority> authorities, boolean enabled, Date lastPasswordResetDate) { this.id = id; this.state = state; this.username = username; this.password = password; this.email = email; this.authorities = authorities; this.enabled = enabled; this.lastPasswordResetDate = lastPasswordResetDate; } public Integer getState() { return state; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } @JsonIgnore @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @JsonIgnore @Override public boolean isAccountNonExpired() { return true; } @JsonIgnore @Override public boolean isAccountNonLocked() { return true; } @JsonIgnore @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return this.enabled; } @JsonIgnore public Integer getId() { return id; } public String getEmail() { return email; } @JsonIgnore public Date getLastPasswordResetDate() { return lastPasswordResetDate; } }
public final class JwtUserFactory { private JwtUserFactory() { } public static JwtUser create(Admins user){ return new JwtUser( user.getAid(), user.getState(), user.getAname(), user.getPwd(), user.getEmail(), mapToGrandAuthroties(user.getAuthorities()), user.getAexist()==1?true:false, user.getLastPasswordResetDate() ); } private static List<GrantedAuthority> mapToGrandAuthroties(List<Authority> authorities) { return authorities.stream() .map(authority -> new SimpleGrantedAuthority(authority.getName().name())) .collect(Collectors.toList()); } }
@Service public class JwtUserDetailServiceImpl implements UserDetailsService { @Autowired private AdminsMapper adminsMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Admins admins = this.adminsMapper.findByUsername(username); if(admins==null){ throw new UsernameNotFoundException("No User found with UserName :"+username); }else{ return JwtUserFactory.create(admins); } } }
public class JwtAuthenticationResponse implements Serializable { private static final long serialVersionUID = 4784951536404964122L; private final String token; public JwtAuthenticationResponse(String token) { this.token = token; } public String getToken() { return this.token; } }
/** * 安全配置类 */ @SuppressWarnings("SpringJavaAutowiringInspection") @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationEntryPoint unauthorizedHandler; @Autowired private UserDetailsService userDetailsService; @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Autowired public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder // 设置 UserDetailsService .userDetailsService(this.userDetailsService) // 使用 BCrypt 进行密码的 hash .passwordEncoder(passwordEncoder()); } /** * 装载 BCrypt 密码编码器 * * @return */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception { return new JwtAuthenticationTokenFilter(); } /** * token请求授权 * * @param httpSecurity * @throws Exception */ @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity // we don‘t need CSRF because our token is invulnerable .csrf().disable() .cors().and() // 跨域 .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() // don‘t create session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() //.antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // allow anonymous resource requests .antMatchers( HttpMethod.GET, "/", "/*.html", "/favicon.ico", "/**/*.html", "/**/*.css", "/**/*.js" ).permitAll() // Un-secure 登录 验证码 .antMatchers( "/api/auth/**", "/api/verifyCode/**", "/api/global_json" ).permitAll() // secure other api .anyRequest().authenticated(); // Custom JWT based security filter // 将token验证添加在密码验证前面 httpSecurity .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); // disable page caching httpSecurity .headers() .cacheControl(); } }
@RequestMapping(value = "/protectedadmin", method = RequestMethod.GET) @PreAuthorize("hasRole(‘ADMIN‘)") public ResponseEntity<?> getProtectedAdmin() { return ResponseEntity.ok("Greetings from admin protected method!"); } @RequestMapping(value = "/protecteduser", method = RequestMethod.GET) @PreAuthorize("hasRole(‘USER‘)") public ResponseEntity<?> getProtectedUser() { return ResponseEntity.ok("Greetings from user protected method!"); }
原文:https://www.cnblogs.com/wutongshu-master/p/10927870.html