.failureHandler(authenticationFailureHandler());
//设置需要认证与拒绝访问的异常处理器
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint());
//3.放行登录url(不需要认证就可以访问)
http.authorizeRequests()
.anyRequest().authenticated();//除了以上资源必须认证才可访问
}
//认证成功处理器
public AuthenticationSuccessHandler authenticationSuccessHandler(){
return (httpServletRequest, httpServletResponse,authentication)-> {
User principal = (User)authentication.getPrincipal();
Map<String,Object> map=new HashMap<>();
map.put(“state”,200);
map.put(“message”,“Login ok”);
Map<String,Object> jwtMap=new HashMap<>();
jwtMap.put(“username”, principal.getUsername());
List authorities = new ArrayList<>();
principal.getAuthorities().forEach((authority)-> {
authorities.add(authority.getAuthority());
});
jwtMap.put(“authorities”,authorities);
String token=JwtUtils.generatorToken(jwtMap);
map.put(“token”, token);
WebUtils.writeJsonToClient(httpServletResponse,map);
};
}
//认证失败处理器
public AuthenticationFailureHandler authenticationFailureHandler(){
return (httpServletRequest, httpServletResponse, e) -> {
Map<String,Object> map=new HashMap<>();
map.put(“state”,500);
map.put(“msg”,“username or password error”);
WebUtils.writeJsonToClient(httpServletResponse,map);
};
}
//没有认证时执行DefaultAuthenticationEntryPoint对象
public AuthenticationEntryPoint authenticationEntryPoint(){
return (httpServletRequest, httpServletResponse, e)->{
Map<String,Object> map=new HashMap<>();
map.put(“state”,401);//SC_UNAUTHORIZED 的值为401
map.put(“message”,“请先登录再访问”);
WebUtils.writeJsonToClient(httpServletResponse,map);
};
}
}
定义业务对象,处理客户端的登陆请求,例如:
package sso.auth.service;
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.基于用户名从数据库查询用户信息
//SysUser user=userMapper.selectUserByUsername(username);//查数据库
if(!“jack”.equals(username))//假设这是从数据库查询的信息
throw new UsernameNotFoundException(“user not exists”);
//2.将用户信息封装到UserDetails对象中并返回
//假设这个密码是从数据库查询出来的
String encodedPwd=passwordEncoder.encode(“123456”);
//假设这个权限信息也是从数据库查询到的
//List permissions=userMapper.selectUserPermissions(username);//查数据库
//假如分配权限的方式是角色,编写字符串时用"ROLE_"做前缀
List grantedAuthorities =
AuthorityUtils.commaSeparatedStringToAuthorityList( “sys:res:retrieve,sys:res:create”);
//这个user是SpringSecurity提供的UserDetails接口的实现,用于封装用户信息
//后续我们也可以基于需要自己构建UserDetails接口的实现
User user=new User(username,encodedPwd,grantedAuthorities);
return user;//这里的返回值会交给springsecurity去校验
}
}
此对象编写好以后,可以启动服务基于postman进行登陆访问测试。
================================================================
第一步:定义JWT工具类,主要用于解析JWT令牌,例如
package sso.resource.util;
public class JwtUtils {
private static String secret=“AAABBBCCCDDDEEE”;
/*基于负载和算法创建token信息/
public static String generatorToken(Map<String,Object> map){
return Jwts.builder()
.setClaims(map)
.setExpiration(new Date(System.currentTimeMillis()+30601000))
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256,secret)
.compact();//签约,创建token
}
/*解析token获取数据/
public static Claims getClaimsFromToken(String token){
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
/*判定token是否失效/
public static boolean isTokenExpired(String token){
Date expiration=getClaimsFromToken(token).getExpiration();
return expiration.before(new Date());
}
}
第二步:定义Web工具类,用于向客户端响应json数据
package sso.auth.util;
public class WebUtils {
public static void writeJsonToClient(HttpServletResponse response, Map<String,Object> map)
throws IOException {
//1设置响应数据的编码
response.setCharacterEncoding(“utf-8”);
//2告诉浏览器响应数据的内容类型以及编码
response.setContentType(“application/json;charset=utf-8”);
//3获取输出流对象
PrintWriter out=response.getWriter();
//4 将map转换为json数据
String result=new ObjectMapper().writeValueAsString(map);
//5 将数据响应到客户端
out.println(result);
out.flush();
}
}
在资源服务中定义权限配置类,默认将所有认证请求放行,例如:
package sso.resource.config;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler());
http.authorizeRequests().anyRequest().permitAll();
}
//没有权限时执行此处理器方法
public AccessDeniedHandler accessDeniedHandler(){
return (httpServletRequest, httpServletResponse, e)-> {
Map<String,Object> map=new HashMap<>();
map.put(“state”,403);//SC_FORBIDDEN的值是403
map.put(“message”,“没有访问权限,请联系管理员”);
WebUtils.writeJsonToClient(httpServletResponse,map);
};
}
}
定义资源服务对象,用于处理客户端的资源访问服务,例如:
package sso.resource.controller;
@RestController
public class ResourceController {
@PreAuthorize(“hasAuthority(‘sys:res:create’)”)
@RequestMapping(“/doCreate”)
public String doCreate(HttpServletResponse response){
return “create resource (insert data) ok”;
}
/*查询操作/
@PreAuthorize(“hasAuthority(‘sys:res:retrieve’)”)
@RequestMapping(“/doRetrieve”)
public String doRetrieve(){//Retrieve 表示查询
return “query resource (select data) ok”;
}
/*修改操作/
@PreAuthorize(“hasAuthority(‘sys:res:update’)”)
@RequestMapping(“/doUpdate”)
public String doUpdate(){
return “update resource (update data) ok”;
}
/*删除操作/
@PreAuthorize(“hasAuthority(‘sys:res:delete’)”)
@RequestMapping(“/doDelete”)
public String doDelete(){
return “delete resource (dalete data) ok”;
}
}
资源服务器中的资源不是所有人都可以访问的,需要具备一定权限才可以,首先我们要判定是否登陆,然后判定登陆用户是否有权限,有访问权限才可以授权访问,这个操作可以放到spring mvc拦截器中进行实现,例如:
第一步:定义Spring MVC 拦截器.
package sso.resource.interceptor;
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token=request.getHeader(“token”);
if(token==null||“”.equals(token)) throw new RuntimeException(“请先登陆”);
if(JwtUtils.isTokenExpired(token)) throw new RuntimeException(“请先登陆”);
Claims claims=JwtUtils.getClaimsFromToken(token);
List list = (List) claims.get(“authorities”);
String[]authorities=list.toArray(new String[]{});
UserDetails userDetails= User.builder()
.username((String)claims.get(“username”))
.password(“”)
.authorities(authorities)
.build();
//将UserDetails对象保存到一个可以和Spring-Security交互的对象中
PreAuthenticatedAuthenticationToken authenticationToken=
new PreAuthenticatedAuthenticationToken(
userDetails,userDetails.getPassword(),
AuthorityUtils.createAuthorityList(authorities));
//将本次解析的用户详情和当前请求关联
//关联之后才能在后面的控制器中获得用户详情
authenticationToken.setDetails(new WebAuthenticationDetails(request));
//将当前用户详情保存到Spring-Security上下文
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
return true;
}
}
第二步:创建Spring Web配置类,用于注册和配置Spring MVC拦截器,例如:
package sso.resource.config;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TokenInterceptor())
.addPathPatterns(“/**”);
}
}
第一步:启动认证服务器,通过postman进行登陆认证,例如:
第二步:启动资源服务器,并基于认证服务器返回的令牌进行资源访问
================================================================
当多个项目都有一部分公共资源需要重复编写时,我们可以创建一个公共工程,在这个工程中创建共性对象和依赖.其它工程需要时直接引用即可.
![在这里插入图片描述]( )初始化工程
第一步:添加项目依赖
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-security
io.jsonwebtoken
jjwt
0.9.1
第二步:拷贝工具类
将sso-auth工程中的WebUtils,JwtUtils拷贝到sso-common工程的sso.util包中
在前后端分离工程中,当通过前端工程访问认证服务和资源服务时,需要进行跨域配置,例如:
package sso.config;
@Configuration
public class CorsFilterConfig {
/*服务端过滤器层面的跨域设计/
@Bean
public FilterRegistrationBean filterFilterRegistrationBean(){
//1.对此过滤器进行配置(跨域设置-url,method)
UrlBasedCorsConfigurationSource configSource=new UrlBasedCorsConfigurationSource();
CorsConfiguration config=new CorsConfiguration();
config.addAllowedHeader(“*”);//所有请求头信息
config.addAllowedMethod(“*”);//所有请求方式,post,delete,get,put,…
config.addAllowedOrigin(“*”);//所有请求参数
config.setAllowCredentials(true);//所有认证信息,例如cookie
//2.注册过滤器并设置其优先级
configSource.registerCorsConfiguration(“/**”, config);
FilterRegistrationBean fBean=
new FilterRegistrationBean(
new CorsFilter(configSource));
//设置此过滤器的优先级最高
fBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return fBean;
}
}
第一步:删除sso-auth,sso-resource 工程下的util包
第二步:删除sso-auth,sso-resource 工程中的公共依赖
第三步:在sso-auth,sso-resource工程中添加通用工程依赖
com.cy.jt
sso-common
1.0-SNAPSHOT
打开postman进行重新登录,访问资源测试.
================================================================
我们做后端,一般在测试时直接基于postman进行访问就可以,为了更好理解前后端通讯过程,我们暂且基于springboot工程构建一个前端工程.
第一步:添加web依赖,代码如下:
org.springframework.boot
spring-boot-starter-web
第二步:创建application.yml配置文件,代码如下:
server:
port: 80
第三步:创建启动类,代码如下:
package sso;
@SpringBootApplication
public class UIApplication {
public static void main(String[] args) {
SpringApplication.run(UIApplication.class,args);
}
}
将课前资料中的static目录直接拷贝到项目中的resource目录下.
登录页面login.html内容如下:
<!doctype html>
Please Login
<button type=“button” @click=“doLogin()” class=“btn btn-primary”>Submit
登录成功页面index.html,代码如下: