1.创建mu-auth
2.添加项目依赖
org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-oauth2 com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config
3.配置文件
server:
port: 8071
spring:
application:
name: mu-auth
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
server-addr: localhost:8848
4.添加启动类
@SpringBootApplication
public class AuthApp {
public static void main(String[] args) {
SpringApplication.run(AuthApp.class, args);
}
}
默认 user 密码如图 登录成功为404 因为没有页面
http://localhost:8071
1.定义安全配置类
package org.mubai.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//关闭跨域攻击(先这么做,没有这句会有403异常)
http.csrf().disable();
//放行所有请求(先这么做)
http.authorizeRequests().anyRequest().permitAll();
//登录成功与失败的处理
http.formLogin()
.successHandler(successHandler())
.failureHandler(failureHandler());
}
@Bean
public AuthenticationSuccessHandler successHandler(){
// return new AuthenticationSuccessHandler() {
// @Override
// public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//
// }
// }
return (request,response,authentication) ->{
//1.构建map对象,封装响应数据
Map map=new HashMap<>();
map.put("state",200);
map.put("message","login ok");
//2.将map对象写到客户端
writeJsonToClient(response,map);
};
}
@Bean
public AuthenticationFailureHandler failureHandler(){
return (request,response, e)-> {
//1.构建map对象,封装响应数据
Map map=new HashMap<>();
map.put("state",500);
map.put("message","login failure");
//2.将map对象写到客户端
writeJsonToClient(response,map);
};
}
private void writeJsonToClient(HttpServletResponse response,
Object object) throws IOException {
//1.将对象转换为json
//将对象转换为json有3种方案:
//1)Google的Gson-->toJson (需要自己找依赖)
//2)阿里的fastjson-->JSON (spring-cloud-starter-alibaba-sentinel)
//3)Springboot web自带的jackson-->writevalueAsString (spring-boot-starter-web)
//我们这里借助springboot工程中自带的jackson
//jackson中有一个对象类型为ObjectMapper,它内部提供了将对象转换为json的方法
//例如:
String jsonStr=new ObjectMapper().writevalueAsString(object);
//3.将json字符串写到客户端
PrintWriter writer = response.getWriter();
writer.println(jsonStr);
writer.flush();
}
}
2.定义用户信息处理对象
package org.mubai.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
//UserDetails用户封装用户信息(认证和权限信息)
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
//1.基于用户名查询用户信息(用户名,用户状态,密码,....)
//Userinfo userinfo=userMapper.selectUserByUsername(username);
String encodedPassword=passwordEncoder.encode("123456");
//2.查询用户权限信息(后面会访问数据库)
//这里先给几个假数据
List authorities =
AuthorityUtils.createAuthorityList(//这里的权限信息先这么写,后面讲
"sys:res:create", "sys:res:retrieve");
//3.对用户信息进行封装
return new User(username,encodedPassword,authorities);
}
}
3.网关中登陆路由配置
- id: router02
uri: lb://mu-auth #lb表示负载均衡,底层默认使用ribbon实现
predicates: #定义请求规则(请求需要按照此规则设计)
- Path=/auth/login/** #请求路径设计/这个是底层自动生成的请求路径
filters:
- StripPrefix=1 #转发之前去掉path中第一层路径
4.发送post请求
5.自定义登陆页面
在sca-resource-ui工程的static目录中定义登陆页面,例如:
login
Please Login
颁发登录成功的令牌
1.构建令牌配置对象
本次我们借助JWT(Json Web Token-是一种json格式)方式将用户相关信息进行组织和加密,并作为响应令牌(Token),从服务端响应到客户端,客户端接收到这个JWT令牌之后,将其保存在客户端(例如localStorage),然后携带令牌访问资源服务器,资源服务器获取并解析令牌的合法性,基于解析结果判定是否允许用户访问资源.
package com.jt.auth.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
@Configuration
public class TokenConfig {
@Bean
public TokenStore tokenStore(){
//这里采用JWT方式生成和存储令牌信息
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter=
new JwtAccessTokenConverter();
//JWT令牌构成:header(签名算法,令牌类型),payload(数据部分),Signing(签名)
//这里的签名可以简单理解为加密,加密时会使用header中算法以及我们自己提供的密钥,
//这里加密的目的是为了防止令牌被篡改。(这里密钥要保管好,要存储在服务端)
jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);//设置密钥
return jwtAccessTokenConverter;
}
private static final String SIGNING_KEY="auth";
}
2.定义认证授权核心配置
@Bean
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
3.所有零件准备好了开始拼装最后的主体部分,这个主体部分就是授权服务器的核心配置
package com.jt.auth.config;
import com.jt.auth.service.UserDetailsServiceImpl;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import java.util.Arrays;
@AllArgsConstructor
@Configuration
@EnableAuthorizationServer
public class Oauth2Config
extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private TokenStore tokenStore;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
//super.configure(endpoints);
endpoints
//由谁完成认证?
.authenticationManager(authenticationManager)
//谁负责访问数据库?(认证时需要两部分信息:一部分来自客户端,一部分来自数据库)
.userDetailsService(userDetailsService)
//支持对什么请求进行认证(默认支持post方式)
.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST)
//认证成功以后令牌如何生成和存储?(默认令牌生成UUID.randomUUID(),存储方式为内存)
.tokenServices(tokenService());
}
//系统底层在完成认证以后会调用TokenService对象的相关方法
//获取TokenStore,基于tokenStore获取token对象
@Bean
public AuthorizationServerTokenServices tokenService(){
//1.构建TokenService对象(此对象提供了创建,获取,刷新token的方法)
DefaultTokenServices tokenServices=
new DefaultTokenServices();
//2.设置令牌生成和存储策略
tokenServices.setTokenStore(tokenStore);
//3.设置是否支持令牌刷新(访问令牌过期了,是否支持通过令牌刷新机制,延长令牌有效期)
tokenServices.setSupportRefreshToken(true);
//4.设置令牌增强(默认令牌会比较简单,没有业务数据,
//就是简单随机字符串,但现在希望使用jwt方式)
TokenEnhancerChain tokenEnhancer=new TokenEnhancerChain();
tokenEnhancer.setTokenEnhancers(Arrays.asList(
new JwtAccessTokenConverter()));
tokenServices.setTokenEnhancer(tokenEnhancer);
//5.设置访问令牌有效期
tokenServices.setAccessTokenValiditySeconds(3600);
//6.设置刷新令牌有效期
tokenServices.setRefreshTokenValiditySeconds(3600*72);//3天
return tokenServices;
}
@Override
public void configure(
AuthorizationServerSecurityConfigurer security)
throws Exception {
//super.configure(security);
security
//1.定义(公开)要认证的url(permitAll()是官方定义好的)
//公开oauth/token_key端点
.tokenKeyAccess("permitAll()") //return this
//2.定义(公开)令牌检查的url
//公开oauth/check_token端点
.checkTokenAccess("permitAll()")
//3.允许客户端直接通过表单方式提交认证
.allowFormAuthenticationForClients();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//super.configure(clients);
clients.inMemory()
//定义客户端的id(客户端提交用户信息进行认证时需要这个id)
.withClient("gateway-client")
//定义客户端密钥(客户端提交用户信息时需要携带这个密钥)
.secret(passwordEncoder.encode("123456"))
//定义作用范围(所有符合规则的客户端)
.scopes("all")
//允许客户端基于密码方式,刷新令牌方式实现认证
.authorizedGrantTypes("password","refresh_token");
}
}
4.配置网管的URL
- id: router02
uri: lb://sca-auth
predicates:
#- Path=/auth/login/** #没要令牌之前,以前是这样配置
- Path=/auth/oauth/** #微服务架构下,需要令牌,现在要这样配置
filters:
- StripPrefix=1
5.Postman访问测试
第一步:启动服务
依次启动sca-auth服务,sca-resource-gateway服务。
第二步:检测sca-auth服务控制台的Endpoints信息,例如:
打开postman进行登陆访问测试
登陆成功会在控制台显示令牌信息,例如:
{
“access_token”: “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2Mjk5OTg0NjAsInVzZXJfbmFtZSI6ImphY2siLCJhdXRob3JpdGllcyI6WyJzeXM6cmVzOmNyZWF0ZSIsInN5czpyZXM6cmV0cmlldmUiXSwianRpIjoiYWQ3ZDk1ODYtMjUwYS00M2M4LWI0ODYtNjIyYjJmY2UzMDNiIiwiY2xpZW50X2lkIjoiZ2F0ZXdheS1jbGllbnQiLCJzY29wZSI6WyJhbGwiXX0.-Zcmxwh0pz3GTKdktpr4FknFB1v23w-E501y7TZmLg4”,
“token_type”: “bearer”,
“refresh_token”: “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJqYWNrIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6ImFkN2Q5NTg2LTI1MGEtNDNjOC1iNDg2LTYyMmIyZmNlMzAzYiIsImV4cCI6MTYzMDI1NDA2MCwiYXV0aG9yaXRpZXMiOlsic3lzOnJlczpjcmVhdGUiLCJzeXM6cmVzOnJldHJpZXZlIl0sImp0aSI6IjIyOTdjMTg2LWM4MDktNDZiZi1iNmMxLWFiYWExY2ExZjQ1ZiIsImNsaWVudF9pZCI6ImdhdGV3YXktY2xpZW50In0.1Bf5IazROtFFJu31Qv3rWAVEtFC1NHWU1z_DsgcnSX0”,
“expires_in”: 3599,
“scope”: “all”,
“jti”: “ad7d9586-250a-43c8-b486-622b2fce303b”
}
登陆页面登陆方法设计
登陆成功以后,将token存储到localStorage中,修改登录页面的doLogin方法,例如
doLogin() {
//1.定义url
let url = "http://localhost:9000/auth/oauth/token"
//2.定义参数
let params = new URLSearchParams()
params.append('username',this.username);
params.append('password',this.password);
params.append("client_id","gateway-client");
params.append("client_secret","123456");
params.append("grant_type","password");
//3.发送异步请求
axios.post(url, params).then((response) => {
alert("login ok");
let result=response.data;
localStorage.setItem("accessToken",result.access_token);
location.href="/fileupload.html";
}).catch((error)=>{
console.log(error);
})
}
登陆页面登陆方法设计
登陆成功以后,将token存储到localStorage中,修改登录页面的doLogin方法,例如
doLogin() {
//1.定义url
let url = "http://localhost:9000/auth/oauth/token"
//2.定义参数
let params = new URLSearchParams()
params.append('username',this.username);
params.append('password',this.password);
params.append("client_id","gateway-client");
params.append("client_secret","123456");
params.append("grant_type","password");
//3.发送异步请求
axios.post(url, params).then((response) => {
alert("login ok");
let result=response.data;
localStorage.setItem("accessToken",result.access_token);
location.href="/fileupload.html";
}).catch((error)=>{
console.log(error);
})
}



