Spring Security OAuth2 JWT實現SSO
阿新 • • 發佈:2018-11-15
轉載務必說明出處:https://blog.csdn.net/LiaoHongHB/article/details/84032850
在這裡我們以一臺伺服器和2臺客戶端做測試,在客戶端1進行登陸之後,訪問客服端2的時候不需要進行登陸就可訪問(類似於淘寶登陸之後,在淘寶網點選天貓連結會發現天貓網已經是登陸狀態)
jwt-server程式碼:
資源伺服器:
@Configuration @EnableWebSecurity public class SsoSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .and() .authorizeRequests() .anyRequest() .authenticated() .and() .csrf().disable(); } }
認證伺服器:
@Configuration @EnableAuthorizationServer public class SsoAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("client1") .secret("clientsecret1") //支援的授權模式(陣列型別) .authorizedGrantTypes("authorization_code", "refresh_token") .scopes("all") .and() .withClient("client2") .secret("clientsecret2") .authorizedGrantTypes("authorization_code", "refresh_token") .scopes("all"); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(jwtTokenStore()).accessTokenConverter(jwtAccessTokenConverter()); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.tokenKeyAccess("isAuthenticated()"); } @Bean public TokenStore jwtTokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); //JWT簽名 converter.setSigningKey("jwt"); return converter; } }
UserDetailService:
@Component public class SsoUserDetailService implements UserDetailsService { @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return new User(username,passwordEncoder.encode("123456"), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN")); } }
Controller:
@RestController
@SessionAttributes("authorizationRequest")
public class SsoApprovalEndpointController {
@RequestMapping({"/oauth/confirm_access"})
public ModelAndView getAccessConfirmation(Map<String, Object> model, HttpServletRequest request) throws Exception {
String template = this.createTemplate(model, request);
if (request.getAttribute("_csrf") != null) {
model.put("_csrf", request.getAttribute("_csrf"));
}
return new ModelAndView(new SsoSpelView(template), model);
}
protected String createTemplate(Map<String, Object> model, HttpServletRequest request) {
String template = TEMPLATE;
if (!model.containsKey("scopes") && request.getAttribute("scopes") == null) {
template = template.replace("%scopes%", "").replace("%denial%", DENIAL);
} else {
template = template.replace("%scopes%", this.createScopes(model, request)).replace("%denial%", "");
}
if (!model.containsKey("_csrf") && request.getAttribute("_csrf") == null) {
template = template.replace("%csrf%", "");
} else {
template = template.replace("%csrf%", CSRF);
}
return template;
}
private CharSequence createScopes(Map<String, Object> model, HttpServletRequest request) {
StringBuilder builder = new StringBuilder("<ul>");
Map<String, String> scopes = (Map)((Map)(model.containsKey("scopes") ? model.get("scopes") : request.getAttribute("scopes")));
Iterator var5 = scopes.keySet().iterator();
while(var5.hasNext()) {
String scope = (String)var5.next();
String approved = "true".equals(scopes.get(scope)) ? " checked" : "";
String denied = !"true".equals(scopes.get(scope)) ? " checked" : "";
String value = SCOPE.replace("%scope%", scope).replace("%key%", scope).replace("%approved%", approved).replace("%denied%", denied);
builder.append(value);
}
builder.append("</ul>");
return builder.toString();
}
private static String CSRF = "<input type='hidden' name='${_csrf.parameterName}' value='${_csrf.token}' />";
private static String DENIAL = "<form id='denialForm' name='denialForm' action='${path}/oauth/authorize' method='post'><input name='user_oauth_approval' value='false' type='hidden'/>%csrf%<label><input name='deny' value='Deny' type='submit'/></label></form>";
private static String TEMPLATE = "<html>" +
"<body>" +
"<div style='display:none;'>"+
"<h1>OAuth Approval</h1>" +
"<p>Do you authorize '${authorizationRequest.clientId}' to access your protected resources?</p>" +
" <form id='confirmationForm' name='confirmationForm' action='${path}/oauth/authorize' method='post'>" +
"<input name='user_oauth_approval' value='true' type='hidden'/>%csrf%%scopes%" +
"<label>" + "<input name='authorize' value='Authorize' type='submit'/></label>" +
"</form>" +
"%denial%" +
"</div>"+
"<script>document.getElementById('confirmationForm').submit()</script>"+
"</body>" +
"</html>";
private static String SCOPE = "<li><div class='form-group'>%scope%: <input type='radio' name='%key%' value='true'%approved%>Approve</input> <input type='radio' name='%key%' value='false'%denied%>Deny</input></div></li>";
}
View.class:
public class SsoSpelView implements View {
private final String template;
private final String prefix;
private final SpelExpressionParser parser = new SpelExpressionParser();
private final StandardEvaluationContext context = new StandardEvaluationContext();
private PropertyPlaceholderHelper.PlaceholderResolver resolver;
public SsoSpelView(String template) {
this.template = template;
this.prefix = new RandomValueStringGenerator().generate() + "{";
this.context.addPropertyAccessor(new MapAccessor());
this.resolver = new PropertyPlaceholderHelper.PlaceholderResolver() {
public String resolvePlaceholder(String name) {
Expression expression = parser.parseExpression(name);
Object value = expression.getValue(context);
return value == null ? null : value.toString();
}
};
}
public String getContentType() {
return "text/html";
}
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
Map<String, Object> map = new HashMap<String, Object>(model);
String path = ServletUriComponentsBuilder.fromContextPath(request).build()
.getPath();
map.put("path", (Object) path==null ? "" : path);
context.setRootObject(map);
String maskedTemplate = template.replace("${", prefix);
PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper(prefix, "}");
String result = helper.replacePlaceholders(maskedTemplate, resolver);
result = result.replace(prefix, "${");
response.setContentType(getContentType());
response.getWriter().append(result);
}
}
client1程式碼:
application.yml:
server:
port: 5001
servlet:
context-path: /client1
security:
oauth2:
client:
client-id: client1
client-secret: clientsecret1
user-authorization-uri: http://127.0.0.1:5000/server/oauth/authorize
access-token-uri: http://127.0.0.1:5000/server/oauth/token
resource:
jwt:
key-uri: http://127.0.0.1:5000/server/oauth/token_key
index.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>sso client2</title>
</head>
<body>
<h1>SSO CLIENT2 DEMO</h1><br>
<a href="http://127.0.0.1:5002/client2/index.jsp">訪問client1</a>
</body>
</html>
client2程式碼:
application.yml:
server:
port: 5002
servlet:
context-path: /client2
security:
oauth2:
client:
client-id: client2
client-secret: clientsecret2
user-authorization-uri: http://127.0.0.1:5000/server/oauth/authorize
access-token-uri: http://127.0.0.1:5000/server/oauth/token
resource:
jwt:
key-uri: http://127.0.0.1:5000/server/oauth/token_key
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>sso client2</title>
</head>
<body>
<h1>SSO CLIENT2 DEMO</h1><br>
<a href="http://127.0.0.1:5001/client1/index.jsp">訪問client1</a>
</body>
</html>
測試:先執行server,然後客戶端。
位址列輸入:localhost:5001/client1/index.jsp --->回車鍵
然後會跳轉到:localhost:5000/sever/login
輸入使用者名稱和密碼:這裡為了方便測試,使用者名稱可以隨便輸,密碼必須為123456
點選Login按鈕即返回到client1/index.jsp頁面
這個時候點選“訪問client2”,在不需要登陸的情況下就可以訪問到client2/index.jsp頁面(即完成單點登陸類似於淘寶登陸天貓)
在client2/index.jsp中訪問client1也是不需要登陸就可訪問了。