1. 程式人生 > >Spring Security OAuth2 JWT實現SSO

Spring Security OAuth2 JWT實現SSO

 轉載務必說明出處: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也是不需要登陸就可訪問了。